#python #oop #reference #pass-by-reference
#python #ооп #ссылка #передача по ссылке
Вопрос:
Проблемы с тем, когда объекты изменены, а когда их нет в Python. Вот мой плохо продуманный пример ниже:
class person:
age = 21
class bar:
def __init__(self, arg1):
self.foo = arg1
self.foo.age = 23
def baz(arg1):
arg1.age = 27
def teh(arg1):
arg1 = [3,2,1]
Person1 = person()
bar1 = bar(Person1)
print Person1.age
print bar1.foo.age
baz(Person1)
print Person1.age
print bar1.foo.age
meh = [1,2,3]
teh(meh)
print meh
Результат таков
23
23
27
27
[1, 2, 3]
Итак, когда мы объявляем Person1, Person1.age равен 21. Ссылка на этот объект передается конструктору класса другого экземпляра класса bar, называемого bar1. Любые изменения, внесенные в эту ссылку, изменят Person1.
Это также тот случай, когда мы передаем Person1 обычной функции, Person1.age теперь равен 27.
Но почему это не работает с переменной «meh»? Конечно, если мы назначим переменную a = meh
и изменим a = [6, 6, 6]
, то meh также изменится. Я в замешательстве. Есть ли какая-либо литература о том, как все это работает?
Комментарии:
1. Чтобы получить правильный отступ кода в StackOverflow, вам нужно использовать пробелы вместо таблиц. Также неплохо сделать это в вашем собственном коде, потому что, я полагаю, это то, что большинство людей делают на Python.
Ответ №1:
Я вижу три фундаментальные концепции Python, которые могут пролить некоторый свет на этот вопрос:
1) Во-первых, назначение из изменяемого объекта, подобного в
self.foo = arg1
похоже на копирование указателя (а не значения, на которое указано): self.foo
и arg1
являются одним и тем же объектом. Вот почему следующая строка,
self.foo.age = 23
изменяет arg1
(т. Е. Person1
). Переменные, таким образом, являются разными «именами» или «метками», которые могут указывать на уникальный объект (здесь person
объект). Это объясняет, почему baz(Person1)
изменяет Person1.age
и bar1.foo.age
на 27, поскольку Person1
и bar1.foo
— это всего лишь два имени для одного и того же person
объекта ( Person1 is bar1.foo
возвращает True
в Python).
2) Вторым важным понятием является понятие присваиваний. В
def teh(arg1):
arg1 = [3,2,1]
переменная arg1
является локальной, так что код
meh = [1,2,3]
teh(meh)
сначала это делает arg1 = meh
, что означает, что arg1
это дополнительное (локальное) имя для списка meh
; но делать arg1 = [3, 2, 1]
это все равно, что сказать «Я передумал: arg1
отныне будет именем нового списка, [3, 2, 1]». Здесь важно иметь в виду, что назначения, несмотря на то, что они обозначаются знаком «равно», асимметричны: они присваивают (изменяемому) объекту справа и сбоку дополнительное имя, указанное в левой части (вот почему вы не можете этого сделать [3, 2, 1] = arg1
, поскольку левая сторона должна быть именем [или именами]). Итак, arg1 = meh; arg1 = [3, 2, 1]
не может измениться meh
.
3) Последний пункт связан с названием вопроса: «передача по значению» и «передача по ссылке» не являются понятиями, которые актуальны в Python. Вместо этого соответствующими понятиями являются «изменяемый объект» и «неизменяемый объект«. Списки изменчивы, а числа — нет, что объясняет то, что вы наблюдаете. Кроме того, ваши объекты Person1
и bar1
изменчивы (вот почему вы можете изменить возраст пользователя). Вы можете найти более подробную информацию об этих понятиях в текстовом руководстве и видеоуроке. В Википедии также есть некоторая (более техническая) информация. Пример иллюстрирует разницу в поведении между изменяемым и неизменяемым:
x = (4, 2)
y = x # This is equivalent to copying the value (4, 2), because tuples are immutable
y = (1, 2, 3) # This does not change x, because tuples are immutable; this does y = (4, 2) (1, 2, 3)
x = [4, 2]
y = x # This is equivalent to copying a pointer to the [4, 2] list
y = [1, 2, 3] # This also changes x, because x and y are different names for the same (mutable) object
Последняя строка не эквивалентна y = y [1, 2, 3]
, потому что это только поместило бы новый объект списка в переменную y
вместо изменения списка, на который ссылаются оба y
и x
.
Три вышеуказанные концепции (переменные как имена [для изменяемых объектов], асимметричное присвоение и изменчивость / неизменяемость) объясняют многие особенности поведения Python.
Комментарии:
1. «Изменяемый объект» против «неизменяемого объекта» на самом деле здесь не очень уместно; проблема в «мутации» против «назначения».
arg1
может использоваться для измененияmeh
, но его нельзя использовать для присвоения нового значенияmeh
.2. @ruakh: Это правда, что код в вопросе касается только присвоения и изменчивости; однако в названии говорится о «передаче по значению и ссылке», поэтому я думаю, что в любом случае полезно упомянуть подход Python к модификации (или нет) переменных, передаваемых вызывающей стороной (т. Е. Изменяемых и неизменяемых объектов).).
3. @ruakh: PS: Тем не менее, я обновил свой пост, чтобы подчеркнуть аспект изменчивости и неизменности.
Ответ №2:
Конечно, если мы присвоим переменной a = meh и изменим a = [6, 6, 6], то meh также будет изменен.
Нет, на самом деле, это не:
>>> meh = [1,2,3]
>>> a = meh
>>> a = [6, 6, 6]
>>> print a
[6, 6, 6]
>>> print meh
[1, 2, 3]
Это разница между перезаписью переменной и изменением экземпляра, на который указывает переменная.
Списки, словари, наборы и объекты являются изменяемыми типами. Если вы добавляете, удаляете, устанавливаете, получаете или иным образом изменяете что-то в их экземпляре, это обновляет все, что ссылается на этот экземпляр.
Однако, если вы назначаете совершенно новый экземпляр типа переменной, это изменяет ссылку, хранящуюся в этой переменной, и, следовательно, старый экземпляр, на который ссылается, не изменяется.
a = [1,2,3] # New instance
a[1] = 4 # Modifying existing instance
b = {'x':1, 'y':2} # New instance
b['x'] = 3 # Modifying existing instance
self.x = [1,2,3] # Modifying existing object instance pointed to by 'self',
# and creating new instance of a list to store in 'self.x'
self.x[0] = 5 # Modifying existing list instance pointed to by 'self.x'
Ответ №3:
В Python нет передачи по значению или передачи по ссылке, но вместо этого используется передача по объекту — другими словами, объекты передаются непосредственно в функции и привязываются к имени параметра, указанному в определении функции.
Когда вы выполняете spam = «green», вы привязываете имя spam к объекту string «green»; если вы затем выполняете eggs = spam, вы ничего не скопировали, вы не создали ссылочных указателей; вы просто привязали другое имя, eggs, к тому же объекту (в данном случае «зеленый»). Если вы затем привяжете спам к чему-то другому (spam = 3.14159), яйца все равно будут привязаны к «зеленому».
В вашей teh
функции вы не изменяете / модифицируете / мутируете переданный объект, вы переназначаете имя arg1
в другой список. Чтобы изменить arg1
, вы хотите это:
def teh(arg1):
arg1[:] = [3, 2, 1]
Комментарии:
1. «Передача по имени» (или «вызов по имени») — это совершенно другое дело, см. cs.sfu.ca /~cameron/Teaching/383/PassByName.html и en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name . Модель передачи аргументов в Python вместо этого описывается как «вызов путем совместного использования», а на других языках с такой же передачей аргументов считается вызовом по значению, причем значение всегда является указателем / ссылкой на объект.
2. @delnan: Спасибо, обновлено до ‘call-by-object’. Python не раскрывает основную механику, используемую для реализации вызова по объекту, поэтому использование термина «вызов по значению» вредно.