Как имя неизменяемого объекта связывается с результатом расширенного присваивания? - PullRequest
5 голосов
/ 20 января 2012

Как имя неизменяемого объекта связывается с результатом расширенного присваивания?

Для изменяемых объектов, например, если x = [1, 2, 3] и y = [4, 5], то когда мы делаем x + = y, он выполняется как x.__iadd__(y), который модифицирует x вместо и имя x снова привязывается к нему?

А как это работает, когда x неизменен? Вот что говорит Python документация об расширенных назначениях.

Если x является экземпляром класса, который не определяет метод __iadd__(), то рассматриваются x.__add__(y) и y.__radd__(x), как и при оценке x + y.

ОК, теперь если x = (1, 2, 3) и y = (4, 5), когда мы делаем x += y, python выполняет x.__add__(y), что создает новый объект. Но когда и как этот новый объект получает отскок до x?

Я запутался в исходном коде CPython, в частности объект tuple ( tupleobject.c ) и AugAssign частей в Python-ast.c и ast.c , но не мог понять, как происходит переплет.

РЕДАКТИРОВАТЬ : Устранены неправильные представления в исходном вопросе и заменены на форму вопроса, чтобы не запутывать будущих читателей.

Ответы [ 2 ]

11 голосов
/ 20 января 2012

Строка, как

x += y

фактически переведено в эквивалент

x = x.__iadd__(y)

Переплет всегда происходит, даже если x изменчив. Если __iadd__() реализован способом, выполняющим операцию на месте, он должен вернуть self, и имя в любом случае возвращается к объекту, на которое оно указывало. Если __iadd__() не реализовано, вместо него используются обычные методы сложения __add__() или __radd__(), как вы уже отмечали в своем посте.

Именно поэтому x += y внутри функции отображает x локальное имя. (Объяснение: В Python имя считается локальным для функции, если внутри функции есть присвоение этому имени. x += y считается присваиванием x, поэтому x считается локальным для функции, содержащей такие см. эту запись в блоге Эли Бендерски для получения дополнительной информации.)

Еще один пример такого поведения:

t = ([], 1)
t[0] += [1]

приводит к ошибке

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

но список все равно добавляется. Это потому, что сначала вызывается list.__iadd__(), меняя список на месте. Затем предпринимается попытка t[0] = <list object>, что приводит к ошибке.

Подробную документацию по семантике можно найти в Справочнике по языку Python и в PEP 203 . Цитата из бывшего:

Расширенное назначение оценивает цель (которая, в отличие от обычных операторов назначения, не может быть распаковкой) и список выражений, выполняет двоичную операцию, специфичную для типа назначения для двух операндов, и присваивает результат исходной цели , Цель оценивается только один раз.

Расширенное выражение присваивания, такое как x + = 1, может быть переписано как x = x + 1 для достижения аналогичного, но не совсем равного эффекта. В расширенной версии x оценивается только один раз. Кроме того, когда это возможно, фактическая операция выполняется на месте, что означает, что вместо создания нового объекта и присвоения его цели, вместо этого изменяется старый объект.

1 голос
/ 20 января 2012

Расширенные присваивания - += *= и т. Д. На самом деле являются присваиваниями, которые связывают левые имена с результатом возврата соответствующего оператора.

Итак, если у вас есть класспредлагает "__iadd__" и выполняет obj += expr для объекта этого класса, вызывается метод obj.__iadd__(expr) и его возвращаемое значение присваивается имени "obj".

Пример:

>>> class A(object):
...    def __iadd__(self, other):
...       return "fixed value"
... 
>>> a = A()
>>> a += ["anything"]
>>> print a
fixed value

Кстати, если класс объекта не определяет расширенные операторы, обычная версия вызывается механизмом Pythonś

...