@AshwiniChaudhary Esa es una distinción bastante sutil, considerando que i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]es así True. Muchos desarrolladores pueden no notar los id(i)cambios para una operación, pero no para la otra.
kojiro
1
@kojiro - Si bien es una distinción sutil, creo que es importante.
mgilson
@mgilson es importante, así que sentí que necesitaba una explicación. :)
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 los 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 aumentar i. En realidad, obtienes un nuevo entero y lo asignas "encima" i, perdiendo una referencia al entero anterior. En este caso, i += 1es exactamente lo mismo que i = i + 1. Pero, con la mayoría de los objetos mutables, es una historia 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]
comparado 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]
Nótese como en el primer ejemplo, puesto by ahacer referencia al mismo objeto, cuando se utiliza +=en b, lo que realmente cambia b(y ave que el cambio también - Después de todo, se hace referencia a la misma lista). Sin embargo, en el segundo caso, cuando lo hago b = b + [1, 2, 3], toma la lista que bhace 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 se ejecuta o si x.__add__(y)vuelve NotImplementedy xy ytienen diferentes tipos , a continuación, x + yintenta llamar y.__radd__(x). Entonces, en el caso donde tienes
foo_instance += bar_instance
si Foono se implementa __add__o __iadd__el resultado aquí es el mismo que
2 En la expresión foo_instance + bar_instance, bar_instance.__radd__se intentará antes foo_instance.__add__si el tipo de bar_instancees una subclase del tipo de foo_instance(por ejemplo issubclass(Bar, Foo)). El racional para esto es porque Bares en cierto sentido un objeto "nivel más alto" que Fooasí Bardebe recibir la opción de anular Fooel comportamiento 's.
Bueno, +=llama __iadd__si existe , y de lo contrario vuelve a agregar y volver a vincular. Por eso i = 1; i += 1funciona a pesar de que no hay int.__iadd__. Pero aparte de esa pequeña mentira, grandes explicaciones.
abarnert
44
@abarnert: siempre supuse que int.__iadd__acababa de llamar __add__. Me alegro de haber aprendido algo nuevo hoy :).
mgilson
@abarnert - supongo que tal vez para ser completado , x + ylas llamadas y.__radd__(x)si x.__add__no existe (o devoluciones NotImplementedy xy yson de diferentes tipos)
mgilson
Si realmente quieres ser completista, deberías mencionar que el bit "si existe" pasa por los mecanismos getattr habituales, a excepción de algunas peculiaridades con las clases clásicas, y para los tipos implementados en la API de C, en su lugar, busca nb_inplace_addo sq_inplace_concat, y esas funciones de API C tienen requisitos más estrictos que los métodos dunder de Python, y ... Pero no creo que sea relevante para la respuesta. La principal distinción es que +=intenta hacer un agregado en el lugar antes de volver a actuar como +, lo que creo que ya has explicado.
abarnert
Sí, supongo que tienes razón ... Aunque podría retroceder en la postura de que la API de C no es parte de Python . Es parte de CPython :-P
mgilson
67
Debajo de las sábanas, i += 1hace algo como esto:
try:
i = i.__iadd__(1)exceptAttributeError:
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 le da a los tipos una forma de manejarlos +=especialmente, creando un __iadd__método y un __add__.
La intención es que los tipos mutables, como list, se muten a sí mismos __iadd__(y luego regresen self, a menos que esté 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 tú mutaste l1, tú 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 rebotó el nombre l1para señalarla, dejando de l2apuntar a la lista original.
(En la +=versión, también estaba volviendo a vincular l1, es solo que en ese caso lo estaba volviendo a listvincular a la misma que ya estaba vinculada, por lo que generalmente puede ignorar esa parte).
en __iadd__realidad llama __add__en caso de un AttributeError?
mgilson
Bueno, i.__iadd__no llama __add__; Es lo i += 1que llama __add__.
abarnert
errr ... Sí, eso es lo que quise decir. Interesante. No me di cuenta de que se hizo automáticamente.
mgilson
3
El primer intento es en realidad i = i.__iadd__(1): iaddpuede modificar el objeto en su lugar, pero no tiene que hacerlo, por lo que se espera que devuelva el resultado en cualquier caso.
lvc
Tenga en cuenta que esto significa que operator.iaddlas llamadas __add__sobre AttributeError, pero no pueden volver a vincular el resultado ... así que i=1; operator.iadd(i, 1)vuelve 2 y hojas ifijan a 1. Lo cual es un poco confuso.
abarnert
6
Aquí hay un ejemplo que se compara directamente i += xcon i = i + x:
def foo(x):
x = x +[42]def bar(x):
x +=[42]
c =[27]
foo(c);# c is not changed
bar(c);# c is changed to [27, 42]
+=
actúa comoextend()
en el caso de las listas.i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
es asíTrue
. Muchos desarrolladores pueden no notar losid(i)
cambios para una operación, pero no para la otra.Respuestas:
Esto depende completamente 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 los 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 porParece aumentar
i
. En realidad, obtienes un nuevo entero y lo asignas "encima"i
, perdiendo una referencia al entero anterior. En este caso,i += 1
es exactamente lo mismo quei = i + 1
. Pero, con la mayoría de los objetos mutables, es una historia diferente:Como ejemplo concreto:
comparado con:
Nótese como en el primer ejemplo, puesto
b
ya
hacer referencia al mismo objeto, cuando se utiliza+=
enb
, lo que realmente cambiab
(ya
ve que el cambio también - Después de todo, se hace referencia a la misma lista). Sin embargo, en el segundo caso, cuando lo hagob = b + [1, 2, 3]
, toma la lista queb
hace referencia y la concatena con una nueva lista[1, 2, 3]
. Luego almacena la lista concatenada en el espacio de nombres actual comob
: sin tener en cuenta cuálb
era la línea anterior.1 En la expresión
x + y
, six.__add__
no se ejecuta o six.__add__(y)
vuelveNotImplemented
yx
yy
tienen diferentes tipos , a continuación,x + y
intenta llamary.__radd__(x)
. Entonces, en el caso donde tienesfoo_instance += bar_instance
si
Foo
no se implementa__add__
o__iadd__
el resultado aquí es el mismo quefoo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2 En la expresión
foo_instance + bar_instance
,bar_instance.__radd__
se intentará antesfoo_instance.__add__
si el tipo debar_instance
es una subclase del tipo defoo_instance
(por ejemploissubclass(Bar, Foo)
). El racional para esto es porqueBar
es en cierto sentido un objeto "nivel más alto" queFoo
asíBar
debe recibir la opción de anularFoo
el comportamiento 's.fuente
+=
llama__iadd__
si existe , y de lo contrario vuelve a agregar y volver a vincular. Por esoi = 1; i += 1
funciona a pesar de que no hayint.__iadd__
. Pero aparte de esa pequeña mentira, grandes explicaciones.int.__iadd__
acababa de llamar__add__
. Me alegro de haber aprendido algo nuevo hoy :).x + y
las llamadasy.__radd__(x)
six.__add__
no existe (o devolucionesNotImplemented
yx
yy
son de diferentes tipos)nb_inplace_add
osq_inplace_concat
, y esas funciones de API C tienen requisitos más estrictos que los métodos dunder de Python, y ... Pero no creo que sea relevante para la respuesta. La principal distinción es que+=
intenta hacer un agregado en el lugar antes de volver a actuar como+
, lo que creo que ya has explicado.Debajo de las sábanas,
i += 1
hace algo como esto:Mientras
i = i + 1
hace algo como esto:Esta es una ligera simplificación excesiva, pero se entiende la idea: Python le da a los tipos una forma de manejarlos
+=
especialmente, creando un__iadd__
método y un__add__
.La intención es que los tipos mutables, como
list
, se muten a sí mismos__iadd__
(y luego regresenself
, a menos que esté haciendo algo muy complicado), mientras que los tipos inmutables, comoint
, simplemente no lo implementarán.Por ejemplo:
Porque
l2
es el mismo objeto quel1
, y tú mutastel1
, tú también mutastel2
.Pero:
Aquí no mutaste
l1
; en su lugar, creó una nueva listal1 + [3]
y rebotó el nombrel1
para señalarla, dejando del2
apuntar a la lista original.(En la
+=
versión, también estaba volviendo a vincularl1
, es solo que en ese caso lo estaba volviendo alist
vincular a la misma que ya estaba vinculada, por lo que generalmente puede ignorar esa parte).fuente
__iadd__
realidad llama__add__
en caso de unAttributeError
?i.__iadd__
no llama__add__
; Es loi += 1
que llama__add__
.i = i.__iadd__(1)
:iadd
puede modificar el objeto en su lugar, pero no tiene que hacerlo, por lo que se espera que devuelva el resultado en cualquier caso.operator.iadd
las llamadas__add__
sobreAttributeError
, pero no pueden volver a vincular el resultado ... así quei=1; operator.iadd(i, 1)
vuelve 2 y hojasi
fijan a1
. Lo cual es un poco confuso.Aquí hay un ejemplo que se compara directamente
i += x
coni = i + x
:fuente