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

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

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

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

2948
David Sykes 12 Июн 2009 в 14:23

23 ответа

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

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

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

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

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

233
Honest Abe 26 Янв 2013 в 09:06

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

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

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

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'
2
Jesse Hogan 10 Сен 2017 в 02:19

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

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

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

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

Illustrated example of passing the argument

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

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

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

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

59
Community 23 Май 2017 в 12:18

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

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

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

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

42
AmanicA 8 Авг 2011 в 10:39

Как вы можете утверждать, у вас должен быть изменяемый объект, но позвольте мне предложить вам проверить глобальные переменные, поскольку они могут помочь вам или даже решить проблему такого рода!

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

Пример:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2
16
iliketocode 9 Авг 2016 в 02:23

Есть небольшая хитрость, чтобы передать объект по ссылке, хотя язык не позволяет это сделать. Он работает и в Java, это список с одним элементом. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

Это безобразный хак, но это работает. ;-П

6
itmuckel 21 Апр 2016 в 16:47

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

Zenadix 25 Май 2016 в 15:30

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

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value
2
Liakos 5 Май 2019 в 14:14

Учитывая то, как python обрабатывает значения и ссылается на них, единственный способ ссылаться на произвольный атрибут экземпляра - по имени:

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

В реальном коде вы, конечно, добавили бы проверку ошибок при поиске dict.

3
mARK bLOORE 31 Окт 2016 в 15:33

Эффбот (он же Фредрик Лунд) описал стиль передачи переменных в 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" (так же, как обновление базы данных будет видно для каждое соединение с этой базой данных).

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

64
Raymond Hettinger 29 Мар 2013 в 04:41

В этом случае переменной с именем var в методе Change присваивается ссылка на self.variable, и вы немедленно присваиваете строку var. Он больше не указывает на self.variable. Следующий фрагмент кода показывает, что произойдет, если вы измените структуру данных, на которую указывают var и self.variable, в данном случае список:

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

Я уверен, что кто-то еще мог бы уточнить это дальше.

18
Mike Mazur 12 Июн 2009 в 10:39

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

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c
4
iliketocode 9 Авг 2016 в 02:22

Поскольку, по-видимому, нигде не упоминается подход к моделированию ссылок, известный, например, из C ++ должен использовать функцию «update» и передавать ее вместо фактической переменной (точнее, «name»):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

Это в основном полезно для «внешних ссылок» или в ситуации с несколькими потоками / процессами (делая функцию обновления потока / многопроцессорной безопасным).

Очевидно, что приведенное выше не позволяет читать значение, а только обновляет его.

1
Daniel Jour 10 Май 2019 в 00:37

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

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

Фактический объект - [0, 1] (который будет называться значением в других языках программирования) передается. Так что на самом деле функция change_me попытается сделать что-то вроде:

[0, 1] = [1, 2, 3]

Что, очевидно, не изменит объект, переданный функции. Если функция выглядела так:

def change_me(list):
   list.append(2)

Тогда вызов приведет к:

[0, 1].append(2)

Что, очевидно, изменит объект. Этот ответ это хорошо объясняет.

10
Community 23 Май 2017 в 11:54

Здесь есть много идей для ответов, но я думаю, что дополнительный момент здесь явно не упоминается. Цитирование из документации по Python https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

«В Python переменные, на которые ссылаются только внутри функции, неявно глобальны. Если переменной назначено новое значение где-либо в теле функции, она считается локальной. Если переменной когда-либо назначается новое значение внутри функции, переменная неявно локальна, и вам нужно явно объявить ее как «глобальную». Хотя сначала это немного удивляет, но это объясняется моментально. С одной стороны, требование глобальной переменной для назначенных переменных обеспечивает защиту от непреднамеренных побочных эффектов. с другой стороны, если бы глобальный требовался для всех глобальных ссылок, вы бы все время использовали глобальный. Вам пришлось бы объявлять как глобальные все ссылки на встроенную функцию или компонент импортируемого модуля. Этот беспорядок будет побеждать полезность глобальной декларации для выявления побочных эффектов ".

Даже при передаче изменяемого объекта в функцию это все равно применяется. И мне ясно объясняется причина различий в поведении между назначением объекта и воздействием на объект в функции.

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

Дает :

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

Следовательно, присвоение глобальной переменной, которая не объявлена глобальной, создает новый локальный объект и разрывает ссылку на исходный объект.

13
Joop 29 Сен 2014 в 07:12

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

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

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

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

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

37
KobeJohn 9 Окт 2015 в 15:00

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

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

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

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

166
MurugananthamS 20 Ноя 2019 в 04:37

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

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

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

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

Примере:

a = 1

Это связывает имя a с объектом целого типа, который содержит значение 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, number, string), путь заключается в том, чтобы обернуть его в изменяемый объект.
Быстрое и грязное решение для этого - одноэлементный список (вместо self.variable, пройти [self.variable] и изменить функцию var[0]).
Более pythonic подход будет заключаться в введении тривиального класса с одним атрибутом. Функция получает экземпляр класса и манипулирует атрибутом.

49
Lutz Prechelt 28 Апр 2019 в 11:24

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

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

    def Change(self):
        self.variable = 'Changed'

В методах экземпляра вы обычно обращаетесь к self для доступа к атрибутам экземпляра. Нормально устанавливать атрибуты экземпляра в __init__ и читать или изменять их в методах экземпляра. Именно поэтому вы передаете self первый аргумент def Change.

Другое решение было бы создать статический метод, подобный этому:

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

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var
8
iliketocode 9 Авг 2016 в 02:23

Проблема возникает из-за неправильного понимания того, какие переменные есть в 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'
667
random_user 3 Апр 2017 в 00:39

У вас есть действительно хорошие ответы здесь.

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' )
23
Alex L 27 Янв 2012 в 04:28

Вы можете просто использовать пустой класс в качестве экземпляра для хранения ссылочных объектов, поскольку внутренние атрибуты объекта хранятся в словаре экземпляра. Смотрите пример.

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)
0
sergzach 3 Май 2018 в 14:14

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

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

Одним из способов является использование global (для глобальных переменных) или nonlocal (для локальных переменных в функции) в функции-обертке.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

Эта же идея работает для чтения и del использования переменной.

Для простого чтения существует даже более короткий способ использования lambda: x, который возвращает вызываемый объект, который при вызове возвращает текущее значение x. Это немного похоже на «звонок по имени», используемый в языках далекого прошлого.

Передача 3-х оболочек для доступа к переменной немного громоздка, поэтому их можно обернуть в класс с атрибутом proxy:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

Поддержка «отражения» Pythons позволяет получить объект, который способен переназначить имя / переменную в заданной области, не определяя функции явно в этой области:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Здесь класс ByRef включает доступ к словарю. Таким образом, атрибут доступа к wrapped преобразуется в доступ к элементу в переданном словаре. Передав результат встроенного locals и имя локальной переменной, вы получите доступ к локальной переменной. Документация Python от 3.5 советует, что изменение словаря может не сработать, но мне кажется, что это работает.

3
textshell 20 Авг 2016 в 14:02