¿Cuándo es diferente "i += x" de "i = i + x" en Python?

Resuelto MarJamRob asked hace 11 años • 3 respuestas

Me dijeron que +=puede tener efectos diferentes a los de la notación estándar i = i +. ¿Hay algún caso en el que i += 1sería diferente i = i + 1?

MarJamRob avatar Mar 13 '13 10:03 MarJamRob
Aceptado

Esto depende enteramente del objeto i.

+=llama al __iadd__método (si existe, recurriendo __add__si no existe) mientras que +llama al __add__método 1 o al __radd__método en algunos casos 2 .

Desde una perspectiva API, __iadd__se supone que se usa para modificar objetos mutables en su lugar (devolviendo el objeto que fue mutado), mientras que __add__debería devolver una nueva instancia de algo. Para objetos inmutables , ambos métodos devuelven una nueva instancia, pero __iadd__colocarán la nueva instancia en el espacio de nombres actual con el mismo nombre que tenía la instancia anterior. Esta es la razón por

i = 1
i += 1

parece incrementarse i. En realidad, obtienes un nuevo número entero y lo asignas "encima de" i, perdiendo una referencia al número entero anterior. En este caso, i += 1es exactamente igual que i = i + 1. Pero, con la mayoría de los objetos mutables, la historia es diferente:

Como ejemplo concreto:

a = [1, 2, 3]
b = a
b += [1, 2, 3]
print(a)  # [1, 2, 3, 1, 2, 3]
print(b)  # [1, 2, 3, 1, 2, 3]

en comparación con:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print(a)  # [1, 2, 3]
print(b)  # [1, 2, 3, 1, 2, 3]

observe cómo en el primer ejemplo, dado que by ahago referencia al mismo objeto, cuando uso +=on b, en realidad cambia b(y ave ese cambio también; después de todo, hace referencia a la misma lista). Sin embargo, en el segundo caso, cuando lo hago b = b + [1, 2, 3], esto toma la lista a la que bse hace referencia y la concatena con una nueva lista [1, 2, 3]. Luego almacena la lista concatenada en el espacio de nombres actual como b: Sin tener en cuenta cuál bera la línea anterior.


1 En la expresión x + y, si x.__add__no está implementada o si x.__add__(y)los retornos NotImplemented y xtienen ytipos diferentes , x + yintenta llamar y.__radd__(x). Entonces, en el caso de que tengas

foo_instance += bar_instance

si Foono se implementa __add__o __iadd__entonces el resultado aquí es el mismo que

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2 En la expresión foo_instance + bar_instance, bar_instance.__radd__se probará antes foo_instance.__add__ si el tipo de bar_instancees una subclase del tipo de foo_instance(p.ej. issubclass(Bar, Foo)). La razón de esto es que, Baren cierto sentido, un objeto de "nivel superior" Foodebería Bartener la opción de anular Fooel comportamiento de .

mgilson avatar Mar 13 '2013 03:03 mgilson

Debajo de las sábanas, i += 1hace algo como esto:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)

Mientras i = i + 1hace algo como esto:

i = i.__add__(1)

Esta es una ligera simplificación excesiva, pero se entiende la idea: Python proporciona a los tipos una manera de manejarlos +=de forma especial, mediante la creación de un __iadd__método además de un archivo __add__.

La intención es que los tipos mutables, como list, se muten __iadd__(y luego regresen self, a menos que estés haciendo algo muy complicado), mientras que los tipos inmutables, como int, simplemente no lo implementarán.

Por ejemplo:

>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]

Porque l2es el mismo objeto que l1, y mutaste l1, también mutaste l2.

Pero:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]

Aquí, no mutaste l1; en su lugar, creó una nueva lista l1 + [3]y recuperó el nombre l1para señalarlo, dejando l2apuntando a la lista original.

(En la +=versión, también estabas volviendo a vincular l1, solo que en ese caso lo estabas volviendo a vincular al mismo modo al listque ya estaba vinculado, por lo que generalmente puedes ignorar esa parte).

abarnert avatar Mar 13 '2013 03:03 abarnert