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

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

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

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

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

23 ответов


Аргументы прошел задание. Обоснование этого двоякое:--22-->

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

так:

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

  • если вы передадите неизменяемые object to a method, вы все еще не можете повторно связать внешнюю ссылку, и вы даже не можете мутировать объект.

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

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

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

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 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 the_string копия outer_string ссылка, и у нас было the_string укажите на новую строку, но не было никакого способа изменить где outer_string указал.

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

EDIT: было отмечено, что это не отвечает на вопрос, который изначально задал @David: "есть ли что-то, что я могу сделать, чтобы передать переменную по фактической ссылке?". Давайте поработаем над этим.

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

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

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

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


проблема возникает из-за непонимания того, какие переменные находятся в 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 является ссылкой на объект string 'Original'. Когда вы звоните Change вы создаете вторую ссылку var на объект. Внутри функции вы переназначаете ссылку var для другого строкового объекта 'Changed', но ссылка self.variable является отдельным и не изменяется.

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

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

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

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


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

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

вот значительная цитата:

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

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


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

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

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. Вы можете только переназначить это, но это не изменит значение исходных переменных.


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

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

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

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

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

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

  • объекты, такие как списки mutable. Это означает, что содержимое объекта может быть изменено чем угодно указывая на объект. Например, x = []; y = x; x.append(10); print y печати [10]. Был создан пустой список. Как "X" и "y" указывают на один и тот же список. The добавить метод мутирует (обновляет) объект списка (например, добавляет запись в базу данных), и результат виден как "x", так и "y" (так же, как обновление базы данных будет видно для каждого подключения к этой базе данных).

надеюсь, что это проясняет вопрос для вас.


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

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

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

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

Illustrated example of passing the argument

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

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

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

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


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

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

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

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


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

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

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

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

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


в 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) присваивает это же имя снова: какому-то другому объекту (который также содержит строку, но может быть чем-то совершенно другим).


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

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

в этом случае переменная под названием 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']
>>> 

Я уверен, что кто-то может прояснить это еще более.


схема pass-by-assignment Python не совсем такая же, как опция ссылочных параметров C++, но она оказывается очень похожей на модель передачи аргументов языка C (и других) на практике:

  • неизменяемые аргументы эффективно передаются"по стоимости."Объекты, такие как целые числа и строки, передаются по ссылке на объект вместо копирования, но поскольку вы не можете изменять неизменяемые объекты на месте, эффект очень похож на создание копировать.
  • изменяемые аргументы, эффективно передают "указатель."Такие объекты, как списки и словари также передаются по объектной ссылке, которая аналогична способу C передает массивы как указатели-изменяемые объекты могут быть изменены в функции, подобно массивы c#.

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

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

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

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

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

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

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


вот простое (я надеюсь) объяснение концепции 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)

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


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

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

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

в случае методов, вы обычно ссылаетесь на self для доступа к атрибутам экземпляра. Это нормально, чтобы установить атрибуты экземпляра в __init__ и прочитайте или измените их в методах экземпляра. Вот почему вы проходите self als первый аргумент для 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

есть небольшой трюк, чтобы передать объект по ссылке, даже если язык не делает это возможным. Он работает и на 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

это грязный хак, но он работает. - P


я использовал следующий метод для быстрого преобразования нескольких кодов Fortran в 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

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

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

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

def change(wrapper):
    wrapper(7)

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

та же идея работает для чтения и deleting переменная.

для просто чтения есть даже более короткий способ просто использовать 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 "reflection" позволяет получить объект, способный переназначить имя/переменную в заданной области без явного определения функций в этой области:

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 переводится на доступ к элементу в переданном словаре. Передавая результат builtin locals и имя локальной переменной это приводит к доступу к локальной переменной. Документация python от 3.5 советует, что изменение словаря может не работать, но, похоже, работает для меня.

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

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

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

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


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

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'

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

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)