Вопрос-Ответ

How do I pass a variable by reference?

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

Я написал этот класс для тестирования:

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

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

Когда я попытался создать экземпляр, результат был Original. Похоже, что параметры в Python передаются по значению. Это правильно? Как я могу изменить код, чтобы получить эффект передачи по ссылке, чтобы результат былChanged?


Иногда людей удивляет, что код типа x = 1, где x - имя параметра, не влияет на аргумент вызывающего объекта, но код типа x[0] = 1 влияет. Это происходит потому, что назначение элемента и назначение фрагмента являются способами видоизменения существующего объекта, а не переназначения переменной, несмотря на = синтаксис. Подробнее см. в разделе Почему функция может изменять одни аргументы так, как это воспринимается вызывающей функцией, но не другие? .

Смотрите также В чем разница между передачей по ссылке и передачей по значению? для важного обсуждения терминологии, не зависящей от языка.

Переведено автоматически
Ответ 1

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


  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, а не его копией, мы можем использовать методы mutating 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)

Output:

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

Since the the_list parameter was passed by value, assigning a new list to it had no effect that the code outside the method could see. The the_list was a copy of the outer_list reference, and we had the_list point to a new list, but there was no way to change where outer_list pointed.

String - an immutable type

It's immutable, so there's nothing we can do to change the contents of the string

Now, let's try to change the reference

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)

Output:

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

Again, since the the_string parameter was passed by value, assigning a new string to it had no effect that the code outside the method could see. The the_string was a copy of the outer_string reference, and we had the_string point to a new string, but there was no way to change where outer_string pointed.

I hope this clears things up a little.

EDIT: It's been noted that this doesn't answer the question that @David originally asked, "Is there something I can do to pass the variable by actual reference?". Let's work on that.

How do we get around this?

As @Andrea's answer shows, you could return the new value. This doesn't change the way things are passed in, but does let you get the information you want back 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)

If you really wanted to avoid using a return value, you could create a class to hold your value and pass it into the function or use an existing class, like a list:

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

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

Ответ 2

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

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

Ответ 4

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

Вызов по объекту

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


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


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

python