Как мне передать переменную по ссылке?
Я написал этот класс для тестирования:
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
Аргументы передаются путем присваивания. Обоснование этого двоякое:
- передаваемый параметр на самом деле является ссылкой на объект (но ссылка передается по значению)
- некоторые типы данных изменяемы, а другие нет
Итак:
Если вы передаете изменяемый объект в метод, метод получает ссылку на этот же объект, и вы можете изменять его к своему удовольствию, но если вы повторно свяжете ссылку в методе, внешняя область ничего не узнает об этом, и после того, как вы закончите, внешняя ссылка по-прежнему будет указывать на исходный объект.
Если вы передаете методу неизменяемый объект, вы все равно не сможете повторно связать внешнюю ссылку и даже не сможете изменить объект.
Чтобы сделать это еще более понятным, давайте приведем несколько примеров.
Список - изменяемый тип
Давайте попробуем изменить список, который был передан методу:
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'
.