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


Документация Python кажется неясной о том, передаются ли параметры по ссылке или значению, а следующий код создает неизмененное значение «Оригинал»,

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

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

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


2024


источник


Ответы:


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

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

Так:

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

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

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

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

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

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заостренный.

Строка - неизменный тип

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

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

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])

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


2191



Проблема возникает из-за непонимания того, какие переменные находятся в 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'

513



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


234



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

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

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

«... переменные [имена] не объекты; они не могут быть обозначены другими переменными или называемыми объектами ».

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


215



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

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

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

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


139



Effbot (он же Фредрик Лунд) описал стиль передачи переменной Python как вызов по-объекту: 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], Пустой список был создан. Оба «x» и «y» указывают на один и тот же список. присоединять метод изменяет (обновляет) объект списка (например, добавляет запись в базу данных), и результат видим как «x», так и «y» (так же, как обновление базы данных будет видимым для каждого подключения к этой базе данных).

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


53



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

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

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

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

Illustrated example of passing the argument

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

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

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

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


52



A simple trick I normally use is to just wrap it in a list:

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

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

(Yeah I know this can be inconvenient, but sometimes it is simple enough to do this.)


36



(edit - Blair has updated his enormously popular answer so that it is now accurate)

I think it is important to note that the current post with the most votes (by Blair Conrad), while being correct with respect to its result, is misleading and is borderline incorrect based on its definitions. While there are many languages (like C) that allow the user to either pass by reference or pass by value, Python is not one of them.

David Cournapeau's answer points to the real answer and explains why the behavior in Blair Conrad's post seems to be correct while the definitions are not.

To the extent that Python is pass by value, all languages are pass by value since some piece of data (be it a "value" or a "reference") must be sent. However, that does not mean that Python is pass by value in the sense that a C programmer would think of it.

If you want the behavior, Blair Conrad's answer is fine. But if you want to know the nuts and bolts of why Python is neither pass by value or pass by reference, read David Cournapeau's answer.


34



There are no variables in Python

The key to understanding parameter passing is to stop thinking about "variables". There are names and objects in Python and together they appear like variables, but it is useful to always distinguish the three.

  1. Python has names and objects.
  2. Assignment binds a name to an object.
  3. Passing an argument into a function also binds a name (the parameter name of the function) to an object.

That is all there is to it. Mutability is irrelevant for this question.

Example:

a = 1

This binds the name a to an object of type integer that holds the value 1.

b = x

This binds the name b to the same object that the name x is currently bound to. Afterwards, the name b has nothing to do with the name x any more.

See sections 3.1 and 4.2 in the Python 3 language reference.


So in the code shown in the question, the statement self.Change(self.variable) binds the name var (in the scope of function Change) to the object that holds the value 'Original' and the assignment var = 'Changed' (in the body of function Change) assigns that same name again: to some other object (that happens to hold a string as well but could have been something else entirely).


24



You got some really good answers here.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )

21



In this case the variable titled var in the method Change is assigned a reference to self.variable, and you immediately assign a string to var. It's no longer pointing to self.variable. The following code snippet shows what would happen if you modify the data structure pointed to by var and self.variable, in this case a list:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

I'm sure someone else could clarify this further.


16