функцию - python указатели




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

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

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

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

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

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


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

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

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

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

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


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

  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 что 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 .

Надеюсь, это немного облегчит ситуацию.

EDIT: Было отмечено, что это не отвечает на вопрос, что @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])

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


В этом случае переменной с self.variable 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']
>>> 

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


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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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


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


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

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'

учитывая, как 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 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)

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

Это уродливый взлом, но он работает. ;-П


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

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

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

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

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