python - utiliza - ¿Cuál es la diferencia entre i=i+1 e i+= 1 en un ciclo ''for''?
for en python 3 (6)
Esta pregunta ya tiene una respuesta aquí:
Descubrí algo curioso hoy y me preguntaba si alguien podría arrojar algo de luz sobre cuál es la diferencia aquí.
import numpy as np
A = np.arange(12).reshape(4,3)
for a in A:
a = a + 1
B = np.arange(12).reshape(4,3)
for b in B:
b += 1
Después de ejecutar cada bucle
for
,
A
no ha cambiado, pero
B
ha agregado uno a cada elemento.
De hecho, uso la versión
B
para escribir en una matriz NumPy inicializada dentro de un bucle
for
.
Como ya se señaló,
b += 1
actualiza
b
en el lugar, mientras que
a = a + 1
calcula
a + 1
y luego asigna el nombre a al resultado (ahora
a
ya no se refiere a una fila de
A
).
Sin embargo, para comprender el operador
+=
correctamente, también debemos comprender el concepto de objetos
mutables
versus
inmutables
.
Considere lo que sucede cuando dejamos de lado la
.reshape
:
C = np.arange(12)
for c in C:
c += 1
print(C) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
Vemos que
C
no se
actualiza, lo que significa que
c += 1
y
c = c + 1
son equivalentes.
Esto se debe a que ahora
C
es una matriz 1D (
C.ndim == 1
) y, por lo tanto, al iterar sobre
C
, cada elemento entero se extrae y se asigna a
c
.
Ahora en Python, los enteros son inmutables, lo que significa que las actualizaciones en el lugar no están permitidas, transformando efectivamente
c += 1
en
c = c + 1
, donde
c
ahora se refiere a un
nuevo
entero, no acoplado a
C
de ninguna manera.
Cuando realiza un bucle sobre las matrices reformadas, se asignan filas enteras (
np.ndarray
''s) a
b
(y
a
) a la vez, que son objetos
mutables
, lo que significa que se le permite pegar nuevos enteros a voluntad, lo que sucede cuando haces
a += 1
.
Cabe mencionar que, aunque
+
y
+=
están relacionados como se describe anteriormente (y muy a menudo lo están), cualquier tipo puede implementarlos de la forma que desee definiendo los métodos
__add__
y
__iadd__
, respectivamente.
En el primer ejemplo, está reasignando la variable
a
, mientras que en el segundo está modificando los datos in situ, utilizando el operador
+=
.
Vea la sección sobre 7.2.1. Declaraciones de asignación aumentadas :
Una expresión de asignación aumentada como
x += 1
puede reescribirse comox = x + 1
para lograr un efecto similar, pero no exactamente igual. En la versión aumentada, x solo se evalúa una vez. Además, cuando sea posible, la operación real se realiza in situ , lo que significa que, en lugar de crear un nuevo objeto y asignarlo al objetivo, el objeto antiguo se modifica en su lugar.
+=
operador llama a
__iadd__
.
Esta función realiza el cambio en el lugar, y solo después de su ejecución, el resultado se vuelve a establecer en el objeto que está "aplicando" el
+=
activado.
__add__
por otro lado toma los parámetros y devuelve su suma (sin modificarlos).
En primer lugar: las variables ayb en los bucles se refieren a objetos
numpy.ndarray
.
En el primer bucle,
a = a + 1
se evalúa de la siguiente manera: se
__add__(self, other)
la función
__add__(self, other)
de
numpy.ndarray
.
Esto crea un nuevo objeto y, por lo tanto, A no se modifica.
Luego, la variable
a
se establece para referirse al resultado.
En el segundo bucle, no se crea ningún objeto nuevo.
La instrucción
b += 1
llama a la función
__iadd__(self, other)
de
numpy.ndarray
que modifica el objeto
ndarray
en el lugar al que se refiere b.
Por lo tanto,
B
se modifica.
La diferencia es que uno modifica la estructura de datos en sí (operación en el lugar)
b += 1
mientras que el otro simplemente
reasigna
la variable
a = a + 1
.
Solo para completar:
x += y
no siempre está
haciendo una operación en el lugar, hay (al menos) tres excepciones:
-
Si
x
no implementa un método__iadd__
, entonces la declaraciónx += y
es solo una abreviatura dex = x + y
. Este sería el caso six
fuera algo así como unint
. -
Si
__iadd__
devuelveNotImplemented
, Python vuelve ax = x + y
. -
El método
__iadd__
podría implementarse teóricamente para no funcionar en su lugar. Sin embargo, sería realmente extraño hacer eso.
Como sucede, sus
b
s son
numpy.ndarray
s que implementa
__iadd__
y se devuelve para que su segundo bucle modifique la matriz original en su lugar.
Puede leer más sobre esto en la documentación de Python de "Emulación de tipos numéricos" .
Estos
__i*__
[__i*__
] están llamados a implementar las asignaciones aritméticas aumentadas (+=
,-=
,*=
,@=
,/=
,//=
,%=
,**=
,<<=
,>>=
,&=
,^=
,|=
). Estos métodos deberían intentar realizar la operación en el lugar (modificando self) y devolver el resultado (que podría ser, pero no tiene que ser, self). Si no se define un método específico, la asignación aumentada recurre a los métodos normales. Por ejemplo, si x es una instancia de una clase con un__iadd__()
,x += y
es equivalente ax = x.__iadd__(y)
. De lo contrario, se consideranx.__add__(y)
ey.__radd__(x)
, como con la evaluación dex + y
. En ciertas situaciones, la asignación aumentada puedea_tuple[i] += ["item"]
errores inesperados (consulte ¿Por quéa_tuple[i] += ["item"]
genera una excepción cuando la suma funciona? ), Pero este comportamiento es, de hecho, parte del modelo de datos.
La forma corta (
a += 1
) tiene la opción de modificar
a
lugar, en lugar de crear un nuevo objeto que represente la suma y volver a vincularlo con el mismo nombre (
a = a + 1
). Entonces, la forma corta (
a += 1
) es mucho más eficiente ya que no necesariamente necesita hacer una copia de
a
diferencia de
a = a + 1
.
Además, incluso si generan el mismo resultado, observe que son diferentes porque son operadores separados:
+
y
+=
Una cuestión clave aquí es que este bucle itera sobre las filas (primera dimensión) de
B
:
In [258]: B
Out[258]:
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
In [259]: for b in B:
...: print(b,''=>'',end='''')
...: b += 1
...: print(b)
...:
[0 1 2] =>[1 2 3]
[3 4 5] =>[4 5 6]
[6 7 8] =>[7 8 9]
[ 9 10 11] =>[10 11 12]
Por lo tanto, el
+=
está actuando sobre un objeto mutable, una matriz.
Esto está implícito en las otras respuestas, pero se pierde fácilmente si su enfoque está en la reasignación a
a = a+1
.
También podría hacer un cambio en el lugar a
b
con
[:]
indexación, o incluso algo más elegante,
b[1:]=0
:
In [260]: for b in B:
...: print(b,''=>'',end='''')
...: b[:] = b * 2
[1 2 3] =>[2 4 6]
[4 5 6] =>[ 8 10 12]
[7 8 9] =>[14 16 18]
[10 11 12] =>[20 22 24]
Por supuesto, con una matriz 2D como
B
generalmente no necesitamos iterar en las filas.
Muchas operaciones que funcionan en un solo
B
también funcionan en todo.
B += 1
,
B[1:] = 0
, etc.