Как передать переменную по ссылке? - PullRequest
2341 голосов
/ 12 июня 2009

В документации Python неясно, передаются ли параметры по ссылке или по значению, и следующий код выдает неизменное значение 'Original'

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

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

Что я могу сделать, чтобы передать переменную по фактической ссылке?

Ответы [ 26 ]

2523 голосов
/ 12 июня 2009

Аргументы передаются по присваиванию . Обоснование этого двоякое:

  1. передаваемый параметр на самом деле является ссылкой на объект (но ссылка передается по значению)
  2. некоторые типы данных являются изменяемыми, но другие не являются

Итак:

  • Если вы передадите объект mutable в метод, метод получит ссылку на этот же объект, и вы сможете изменить его на радость своего сердца, но если вы перепроверете ссылку в методе внешняя область не будет ничего знать об этом, и после того, как вы закончите, внешняя ссылка все еще будет указывать на исходный объект

  • Если вы передадите неизменяемый объект методу, вы все равно не сможете повторно привязать внешнюю ссылку и даже не можете мутировать объект.

Чтобы было еще яснее, давайте приведем несколько примеров.

Список - изменяемый тип

Попробуем изменить список, который был передан методу:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Выход:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Поскольку переданный параметр является ссылкой на outer_list, а не его копией, мы можем использовать методы списка мутаций, чтобы изменить его и отразить изменения во внешней области.

Теперь давайте посмотрим, что происходит, когда мы пытаемся изменить ссылку, переданную в качестве параметра:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Выход:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Поскольку параметр the_list был передан по значению, присвоение ему нового списка не имело эффекта, который мог видеть код вне метода. the_list был копией ссылки outer_list, и мы the_list указывали на новый список, но не было никакого способа изменить, куда указывал outer_list.

String - неизменный тип

Он неизменен, поэтому мы ничего не можем сделать, чтобы изменить содержимое строки

Теперь давайте попробуем изменить ссылку

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Выход:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

Опять же, поскольку параметр the_string был передан по значению, присвоение ему новой строки не имело эффекта, который мог видеть код вне метода. the_string был копией ссылки outer_string, и мы the_string указывали на новую строку, но не было никакого способа изменить то, куда указывал outer_string.

Надеюсь, это немного прояснит ситуацию.

РЕДАКТИРОВАТЬ: Было отмечено, что это не отвечает на вопрос, который первоначально задавал @David: «Есть ли что-то, что я могу сделать, чтобы передать переменную по фактической ссылке?». Давайте работать над этим.

Как нам обойти это?

Как показывает ответ @ Andrea, вы можете вернуть новое значение. Это не меняет способ передачи данных, но позволяет получить информацию, которую вы хотите вернуть:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Если вы действительно хотите избежать использования возвращаемого значения, вы можете создать класс для хранения вашего значения и передать его в функцию или использовать существующий класс, например, список:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Хотя это кажется немного громоздким.

609 голосов
/ 15 ноября 2011

Проблема возникает из-за неправильного понимания того, какие переменные есть в Python. Если вы привыкли к большинству традиционных языков, у вас есть ментальная модель того, что происходит в следующей последовательности:

a = 1
a = 2

Вы считаете, что a - это область памяти, в которой хранится значение 1, а затем обновляется для хранения значения 2. Это не так, как все работает в Python. Скорее, a начинается как ссылка на объект со значением 1, а затем переназначается как ссылка на объект со значением 2. Эти два объекта могут продолжать сосуществовать, даже если a больше не относится к первому объекту; фактически они могут быть разделены любым количеством других ссылок в программе.

Когда вы вызываете функцию с параметром, создается новая ссылка, которая ссылается на переданный объект. Она отделена от ссылки, которая использовалась в вызове функции, поэтому нет способа обновить эту ссылку и сделать ее обратитесь к новому объекту. В вашем примере:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

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

self.variable является ссылкой на строковый объект 'Original'. Когда вы вызываете Change, вы создаете вторую ссылку var на объект. Внутри функции вы переназначаете ссылку var на другой строковый объект 'Changed', но ссылка self.variable является отдельной и не изменяется.

Единственный способ обойти это - передать изменяемый объект. Поскольку обе ссылки ссылаются на один и тот же объект, любые изменения объекта отражаются в обоих местах.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'
287 голосов
/ 04 сентября 2014

Другие ответы были довольно длинными и сложными, поэтому я создал эту простую диаграмму, чтобы объяснить, как Python обрабатывает переменные и параметры. enter image description here

227 голосов
/ 12 июня 2009

Это не передача по значению или передача по ссылке - это вызов по объекту. Посмотри на это, Фредрик Лунд:

http://effbot.org/zone/call-by-object.htm

Вот важная цитата:

"... переменные [имена] являются не объектами; их нельзя обозначать другими переменными или ссылаться на объекты."

В вашем примере, когда вызывается метод Change - для него создается пространство имен ; и var становится именем в этом пространстве имен для строкового объекта 'Original'. Этот объект затем имеет имя в двух пространствах имен. Затем var = 'Changed' связывает var с новым строковым объектом, и, таким образом, пространство имен метода забывает о 'Original'. Наконец, это пространство имен забыто, и строка 'Changed' вместе с ней.

157 голосов
/ 12 июня 2009

Думайте о вещах, переданных по присваиванию вместо ссылки / по значению. Таким образом, всегда понятно, что происходит, если вы понимаете, что происходит во время обычного задания.

Таким образом, при передаче списка функции / методу список присваивается имени параметра. Добавление к списку приведет к изменению списка. Переназначая список внутри , функция не изменит исходный список, поскольку:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

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

60 голосов
/ 29 марта 2013

Effbot (он же Фредрик Лунд) описал стиль передачи переменных в Python как call-by-object: http://effbot.org/zone/call-by-object.htm

Объекты размещаются в куче, и указатели на них можно передавать где угодно.

  • Когда вы делаете присвоение, такое как x = 1000, создается словарная запись, которая отображает строку «x» в текущем пространстве имен на указатель на целочисленный объект, содержащий тысячу.

  • Когда вы обновляете «x» с помощью x = 2000, создается новый целочисленный объект и словарь обновляется, чтобы указывать на новый объект. Старый объект тысяча неизменен (и может или не может быть живым в зависимости от того, относится ли что-либо еще к объекту).

  • Когда вы делаете новое назначение, такое как y = x, создается новая словарная запись "y", которая указывает на тот же объект, что и запись для "x".

  • Объекты типа строк и целых чисел неизменны . Это просто означает, что нет методов, которые могут изменить объект после его создания. Например, как только создается целочисленный объект тысяча, он никогда не изменится. Математика выполняется путем создания новых целочисленных объектов.

  • Объекты, подобные спискам, изменяемые . Это означает, что содержимое объекта может быть изменено любым указателем на объект. Например, x = []; y = x; x.append(10); print y напечатает [10]. Пустой список создан. И «х», и «у» указывают на один и тот же список. Метод append изменяет (обновляет) объект списка (например, добавляет запись в базу данных), и результат виден как "x", так и "y" (точно так же, как обновление базы данных будет видно каждому подключение к этой базе данных).

Надеюсь, это прояснит проблему для вас.

59 голосов
/ 15 сентября 2012

Технически, Python всегда использует проход по ссылочным значениям . Я собираюсь повторить мой другой ответ , чтобы поддержать мое утверждение.

Python всегда использует значения передачи по ссылке. Нет никаких исключений. Любое присвоение переменной означает копирование контрольного значения. Не исключение Любая переменная - это имя, связанное с эталонным значением. Всегда.

Вы можете думать о ссылочном значении как адрес целевого объекта. Адрес автоматически разыменовывается при использовании. Таким образом, работая с эталонным значением, кажется, что вы работаете непосредственно с целевым объектом. Но между ними всегда есть ссылка, еще один шаг, чтобы перейти к цели.

Вот пример, который доказывает, что Python использует передачу по ссылке:

Illustrated example of passing the argument

Если аргумент был передан по значению, внешний lst не может быть изменен. Зеленый - это целевые объекты (черный - это значение, хранящееся внутри, красный - это тип объекта), желтый - это память с эталонным значением внутри - нарисованная как стрелка. Синяя сплошная стрелка - это эталонное значение, которое было передано функции (через пунктирный путь синей стрелки). Уродливый темно-желтый - это внутренний словарь. (На самом деле его можно нарисовать также в виде зеленого эллипса. Цвет и форма говорят только о том, что он внутренний.)

Вы можете использовать встроенную функцию id(), чтобы узнать, что такое контрольное значение (то есть адрес целевого объекта).

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

Справочные значения скрыты в Python. Не существует явного пользовательского типа для хранения ссылочного значения. Тем не менее, вы можете использовать элемент списка (или элемент в любом другом подходящем типе контейнера) в качестве ссылочной переменной, потому что все контейнеры хранят элементы также как ссылки на целевые объекты. Другими словами, элементы на самом деле не содержатся внутри контейнера - только ссылки на элементы.

42 голосов
/ 06 августа 2011

Простой трюк, который я обычно использую, - это просто обернуть его в список:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Да, я знаю, что это может быть неудобно, но иногда это достаточно просто сделать.)

37 голосов
/ 11 февраля 2014

В Python нет переменных

Ключ к пониманию передачи параметров - перестать думать о «переменных». В Python есть имена и объекты, и вместе они появляются как переменные, но полезно всегда различать три.

  1. Python имеет имена и объекты.
  2. Назначение привязывает имя к объекту.
  3. Передача аргумента в функцию также связывает имя (имя параметра функции) с объектом.

Это все, что нужно сделать. Изменчивость не имеет значения для этого вопроса.

Пример:

a = 1

Это связывает имя a с объектом типа integer, который содержит значение 1.

b = x

Это связывает имя b с тем же объектом, с которым в данный момент связано имя x. Впоследствии имя b больше не имеет ничего общего с именем x.

См. Разделы 3.1 и 4.2 в справочнике по языку Python 3.

Как прочитать пример в вопросе

В коде, показанном в вопросе, оператор self.Change(self.variable) связывает имя var (в области действия Change) с объектом, который содержит значение 'Original' и присваивание var = 'Changed' (в тело функции Change) снова присваивает то же имя: другому объекту (который также содержит строку, но мог бы быть чем-то другим).

Как пройти по ссылке

(редактировать 2019-04-28)

Так что, если вещь, которую вы хотите изменить, является изменяемым объектом, проблем не возникает, так как все эффективно передается по ссылке.

Если это неизменяемый объект (например, bool, число, строка), способ состоит в том, чтобы обернуть его в изменяемый объект.
Быстрое решение для этого - одноэлементный список (вместо self.variable, передать [self.variable] и в функции modify var[0]).
Более pythonic подход будет состоять в том, чтобы ввести тривиальный класс с одним атрибутом. Функция получает экземпляр класса и управляет атрибутом.

36 голосов
/ 27 июня 2010

(редактировать - Блэр обновил свой чрезвычайно популярный ответ, чтобы теперь он был точным)

Я думаю, что важно отметить, что текущий пост, набравший наибольшее количество голосов (Блэр Конрад), будучи верным в отношении своего результата, вводит в заблуждение и является неправильным на грани на основе его определений. Хотя существует много языков (например, C), которые позволяют пользователю передавать по ссылке или передавать по значению, Python не является одним из них.

Ответ Дэвида Курнапо указывает на реальный ответ и объясняет, почему поведение в посте Блэра Конрада кажется правильным, а определения - нет.

В той степени, в которой Python передается по значению, все языки передаются по значению, поскольку должен быть отправлен некоторый фрагмент данных (будь то «значение» или «ссылка»). Тем не менее, это не означает, что Python передается по значению в том смысле, что программист на Си думает об этом.

Если вам нужно поведение, ответ Блэр Конрад в порядке. Но если вы хотите знать, почему Python не передается по значению или по ссылке, прочитайте ответ Дэвида Курнапо.

...