НейроАгент

Передача по ссылке в Python: Полное руководство

Узнайте, как работает передача по ссылке на объекты в Python с изменяемыми и неизменяемыми объектами. Полное руководство с примерами модификации переменных в функциях.

Вопрос

Как передать переменную по ссылке в Python?

Я написал этот класс для тестирования:

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Когда я попытался создать экземпляр, вывод был Original. Похоже, что в Python параметры передаются по значению. Это так? Как мне изменить код, чтобы получить эффект передачи по ссылке, чтобы вывод был Changed?

Иногда люди удивляются, что код вроде x = 1, где x - это имя параметра, не влияет на аргумент вызывающего кода, а код вроде x[0] = 1 влияет. Это происходит потому, что присваивание элементам и срезам - это способы изменения существующего объекта, а не переназначения переменной, несмотря на синтаксис =. Подробнее см. Почему функция может изменять некоторые аргументы, как воспринимается вызывающим кодом, а другие - нет?

См. также В чем разница между передачей по ссылке и передачей по значению? для важного обсуждения терминологии, не зависящей от языка программирования.

НейроАгент

Python не поддерживает традиционную передачу по ссылке, как языки такие как C++ или Java. Вместо этого Python использует механизм передачи по ссылке на объект, при котором вы передаете ссылки на объекты, а не сами объекты. Ваш код не работает, потому что строки в Python являются неизменяемыми (immutable) - когда вы присваиваете var = 'Changed', вы создаете новый строковый объект и привязываете параметр var к нему, а не изменяете исходный объект.


Содержание


Понимание механизма передачи параметров в Python

Python использует подход передачи через присваивание (pass-by-assignment), который сочетает в себе элементы как передачи по значению, так и передачи по ссылке. Когда вы передаете аргумент в функцию, Python создает связь между именем параметра функции и переданным объектом.

Как объясняется на Real Python: “Некоторые языки обрабатывают аргументы функций как ссылки на существующие переменные, что известно как передача по ссылке. Другие языки обрабатывают их как независимые значения, подход, известный как передача по значению.”

Ключевое понимание заключается в том, что Python всегда передает ссылки на объекты. Будет ли это вести себя как передача по значению или по ссылке, зависит от того, является ли объект изменяемым или неизменяемым:

  • Изменяемые объекты (списки, словари, множества): Могут быть изменены на месте
  • Неизменяемые объекты (строки, числа, кортежи): Не могут быть изменены после создания

Почему ваш код не работает: Неизменяемые объекты

Ваш код не работает, потому что self.variable является строкой, которая в Python является неизменяемой. Когда вы вызываете change(self.variable), Python создает новую связь, при которой параметр var ссылается на тот же строковый объект, что и self.variable. Однако при выполнении var = 'Changed' вы не изменяете исходную строку - вы создаете новую строку и перенаправляете var на указание на нее.

Статья в Medium о передаче параметров в Python прекрасно объясняет это: “При a = 3 у нас есть переменная a, ссылающаяся на объект со значением 3.”

Вот что происходит шаг за шагом:

python
# В вашем методе __init__:
self.variable = 'Original'  # Создается строковый объект 'Original'
self.change(self.variable)  # Передается ссылка на строку 'Original'

# В методе change:
def change(self, var):      # var теперь ссылается на тот же объект, что и self.variable
    var = 'Changed'         # Создается новая строка 'Changed', var теперь указывает на нее
    # Исходная строка 'Original' остается неизменной

Как добиться поведения передачи по ссылке

Чтобы достичь желаемого эффекта, вам нужно изменять атрибут экземпляра напрямую, а не пытаться передать строковое значение. Вот несколько подходов:

Метод 1: Передайте сам экземпляр

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self)
        print(self.variable)

    def change(self, instance):
        instance.variable = 'Changed'

Метод 2: Используйте изменяемый контейнер

python
class PassByReference:
    def __init__(self):
        self.variable = ['Original']  # Используйте список вместо строки
        self.change(self.variable)
        print(self.variable[0])

    def change(self, var):
        var[0] = 'Changed'  # Измените содержимое списка

Метод 3: Верните новое значение и присвойте его

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.variable = self.change(self.variable)
        print(self.variable)

    def change(self, var):
        return 'Changed'

Изменяемые и неизменяемые объекты в вызовах функций

Поведение, с которым вы сталкиваетесь, обусловлено фундаментальным различием между изменяемыми и неизменяемыми объектами в Python:

Изменяемые объекты (Могут быть изменены)

  • Списки, словари, множества, пользовательские классы
  • При передаче в функции их содержимое может быть изменено
  • Исходный объект в области видимости вызывающей стороны отражает эти изменения
python
def modify_list(lst):
    lst.append('new element')  # Изменяет исходный список

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # Вывод: [1, 2, 3, 'new element']

Неизменяемые объекты (Не могут быть изменены)

  • Строки, числа, кортежи
  • При передаче в функции любое присваивание создает новые объекты
  • Исходный объект в области видимости вызывающей стороны остается неизменным
python
def modify_string(s):
    s = s + ' modified'  # Создает новую строку, не влияет на исходную

my_string = 'hello'
modify_string(my_string)
print(my_string)  # Вывод: 'hello' (неизменен)

Как указано на GeeksforGeeks: “Изменяется ли исходные данные, зависит от того, является ли объект изменяемым (может быть изменен на месте) или неизменяемым (не может быть изменен после создания).”

Практические примеры и лучшие практики

Пример 1: Изменение словаря

python
def add_to_dict(d, key, value):
    d[key] = value  # Это изменяет исходный словарь

my_dict = {'name': 'Alice'}
add_to_dict(my_dict, 'age', 25)
print(my_dict)  # Вывод: {'name': 'Alice', 'age': 25}

Пример 2: Изменение содержимого списка

python
def modify_list_contents(lst):
    lst[0] = 'changed'  # Это работает - изменяет существующий список
    # lst = ['new']  # Это не работает - создает новую ссылку на список

my_list = ['original', 2, 3]
modify_list_contents(my_list)
print(my_list)  # Вывод: ['changed', 2, 3]

Пример 3: Ловушка “присваивания”

python
def reassign_parameter(param):
    param = 'new value'  # Это влияет только на локальный параметр

my_var = 'original'
reassign_parameter(my_var)
print(my_var)  # Вывод: 'original' - без изменений

Распространенные ловушки и решения

Ловушка 1: Путаница между присваиванием и изменением

python
def problematic_function(x):
    x = 10  # Присваивание - создает новый объект
    x[0] = 10  # Изменение - изменяет существующий объект (работает только для изменяемых)

# Для неизменяемых объектов присваивание не влияет на вызывающего
# Для изменяемых объектов изменение влияет на вызывающего, но присваивание - нет

Ловушка 2: Изменяемые аргументы по умолчанию

python
def bad_function(items=[]):  # Опасно - один и тот же объект списка используется повторно
    items.append('item')
    return items

print(bad_function())  # ['item']
print(bad_function())  # ['item', 'item'] - проблема!

# Решение: Используйте None в качестве значения по умолчанию
def good_function(items=None):
    if items is None:
        items = []
    items.append('item')
    return items

Решение: Используйте четкую документацию

python
def process_data(data):
    """
    Изменяет переданный объект данных на месте.
    Для неизменяемых данных возвращает измененную копию.
    """
    if isinstance(data, (str, int, float, tuple)):
        # Обрабатываем неизменяемые объекты, возвращая новый объект
        return f"processed_{data}"
    else:
        # Обрабатываем изменяемые объекты, изменяя их на месте
        data.processed = True
        return data  # Возвращаем для единообразия

Заключение

Чтобы обобщить ключевые моменты о механизме передачи параметров в Python:

  1. Python использует передачу по ссылке на объект, а не традиционную передачу по значению или по ссылке
  2. Неизменяемые объекты (строки, числа, кортежи) не могут быть изменены - любое присваивание создает новые объекты
  3. Изменяемые объекты (списки, словари, множества) могут быть изменены на месте, влияя на исходный объект
  4. Чтобы достичь желаемого эффекта в вашем коде, передавайте сам экземпляр методу change, а не атрибут экземпляра, как показано в Методе 1 выше. Это дает вам прямой доступ для изменения состояния объекта, а не попытки изменить неизменяемые строковые значения
  5. Ключевое различие заключается между присваиванием (которое всегда создает новые связи) и изменением (которое может изменять существующие объекты для изменяемых типов)

Источники

  1. Pass by reference vs value in Python - GeeksforGeeks
  2. Understanding Python’s Pass by Assignment - Medium
  3. Pass-by-value, reference, and assignment - mathspp
  4. Pass by Reference in Python: Background and Best Practices – Real Python
  5. Tricky Python II: Parameter Passing for Mutable & Immutable Objects - Medium
  6. Python: Passing mutable object to method - Stack Overflow
  7. Why can a function modify some arguments as perceived by the caller, but not others? - Stack Overflow