python - ¿Por qué funciona b+=(4,) y b=b+(4,) no funciona cuando b es una lista?
python-3.x tuples (7)
Como se explica
here
, si la
array
no implementa el método
__iadd__
,
b+=(4,)
sería solo una abreviatura de
b = b + (4,)
pero obviamente no lo es, por lo que la
array
implementa el método
__iadd__
.
Aparentemente, la implementación del método
__iadd__
es algo como esto:
def __iadd__(self, x):
self.extend(x)
Sin embargo, sabemos que el código anterior no es la implementación real del método
__iadd__
, pero podemos suponer y aceptar que hay algo como
extend
método
extend
, que acepta entradas de
tupple
.
Si tomamos
b = [1,2,3]
y si intentamos hacer:
b+=(4,)
Devuelve
b = [1,2,3,4]
, pero si intentamos hacer
b = b + (4,)
no funciona.
b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can''t add tuples and lists
Esperaba que
b+=(4,)
fallara ya que no puede agregar una lista y una tupla, pero funcionó.
Así que intenté
b = b + (4,)
esperando obtener el mismo resultado, pero no funcionó.
Cuando haces esto:
b += (4,)
se convierte a esto:
b.__iadd__((4,))
Bajo el capó se llama
b.extend((4,))
,
extend
acepta un iterador y por eso esto también funciona:
b = [1,2,3]
b += range(2) # prints [1, 2, 3, 0, 1]
pero cuando haces esto:
b = b + (4,)
se convierte a esto:
b = b.__add__((4,))
aceptar solo objeto de lista.
De los documentos oficiales, para los tipos de secuencia mutable, ambos:
s += t
s.extend(t)
se definen como:
extiende
s
con los contenidos det
Lo cual es diferente de ser definido como:
s = s + t # not equivalent in Python!
Esto también significa que
cualquier tipo de secuencia funcionará para
t
, incluida una tupla como en su ejemplo.
¡Pero también funciona para rangos y generadores! Por ejemplo, también puedes hacer:
s += range(3)
El problema con las preguntas de "por qué" es que generalmente pueden significar varias cosas diferentes. Trataré de responder a cada una que creo que tengas en mente.
"¿Por qué es posible que funcione de manera diferente?"
lo cual es respondido por ej.
this
.
Básicamente,
+=
intenta usar diferentes métodos del objeto:
__iadd__
(que solo está marcado en el lado izquierdo), vs
__add__
y
__radd__
("suma inversa", marcado en el lado derecho si está en el lado izquierdo no tiene
__add__
) para
+
.
"¿Qué hace exactamente cada versión?"
En resumen, el método
list.__iadd__
hace lo mismo que
list.extend
(pero debido al diseño del lenguaje, todavía hay una asignación de vuelta).
Esto también significa, por ejemplo, que
>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]
+
, por supuesto, crea un nuevo objeto, pero explícitamente requiere otra lista en lugar de intentar extraer elementos de una secuencia diferente.
"¿Por qué es útil que + = haga esto?
Es más eficiente; el método
extend
no tiene que crear un nuevo objeto. Por supuesto, esto tiene algunos efectos sorprendentes a veces (como arriba), y en general Python no se trata realmente eficiencia, pero estas decisiones se tomaron hace mucho tiempo.
"¿Cuál es la razón para no permitir agregar listas y tuplas con +?"
Ver
.com/questions/9897070/…
(gracias, @ splash58);
Una idea es que (tupla + lista) debería producir el mismo tipo que (lista + tupla), y no está claro de qué tipo debería ser el resultado.
+=
no tiene este problema, porque
a += b
obviamente no debería cambiar el tipo de
a
.
La mayoría de la gente esperaría que X + = Y sea equivalente a X = X + Y. De hecho, la Referencia de bolsillo de Python (4ª edición) de Mark Lutz dice en la página 57 "Los siguientes dos formatos son más o menos equivalentes: X = X + Y, X + = Y ". Sin embargo, las personas que especificaron Python no los hicieron equivalentes. Posiblemente fue un error que resultará en horas de tiempo de depuración por parte de programadores frustrados durante el tiempo que Python permanezca en uso, pero ahora es exactamente la forma en que está Python. Si X es un tipo de secuencia mutable, X + = Y es equivalente a X.extend (Y) y no a X = X + Y.
Los operadores de asignación "aumentada" como
+=
se introdujeron en Python 2.0, que se lanzó en octubre de 2000. El diseño y la justificación se describen en
PEP 203
.
Uno de los objetivos declarados de estos operadores era el apoyo de las operaciones en el lugar.
Escritura
a = [1, 2, 3]
a += [4, 5, 6]
se supone que actualiza la lista
en su lugar
.
Esto es importante si hay otras referencias a la lista
a
, por ejemplo, cuando se recibió a como un argumento de función.
Sin embargo, la operación no siempre puede suceder en el lugar, ya que muchos tipos de Python, incluidos los enteros y las cadenas, son
inmutables
, por lo que, por ejemplo,
i += 1
para un entero que no puedo operar en su lugar.
En resumen, se suponía que los operadores de asignación aumentada trabajarían en su lugar cuando fuera posible y, de lo contrario, crearían un nuevo objeto.
Para facilitar estos objetivos de diseño, la expresión
x += y
se especificó para comportarse de la siguiente manera:
-
Si se define
x.__iadd__(y)
se evalúax.__iadd__(y)
. -
De lo contrario, si se implementa
x.__add__
sex.__add__
x.__add__(y)
. -
De lo contrario, si se implementa
y.__radd__(x)
se evalúay.__radd__(x)
. - De lo contrario, genera un error.
El primer resultado obtenido por este proceso se asignará nuevamente a
x
(a menos que ese resultado sea el singleton
NotImplemented
, en cuyo caso la búsqueda continúa con el siguiente paso).
Este proceso permite que los tipos que admiten modificaciones en el lugar implementen
__iadd__()
.
Los tipos que
no
admiten la modificación en el lugar no necesitan agregar ningún método mágico nuevo, ya que Python volverá automáticamente a esencialmente
x = x + y
.
Así que finalmente lleguemos a su pregunta real: por qué puede agregar una tupla a una lista con un operador de asignación aumentada.
De memoria, el historial de esto fue más o menos así: El método
list.__iadd__()
se implementó simplemente para llamar al método
list.extend()
ya existente en Python 2.0.
Cuando se introdujeron los iteradores en Python 2.1, el método
list.extend()
se actualizó para aceptar iteradores arbitrarios.
El resultado final de estos cambios fue que
my_list += my_tuple
funcionó a partir de Python 2.1.
Sin embargo, nunca se suponía que el método
list.__add__()
admitiera iteradores arbitrarios como argumento de la derecha; esto se consideró inapropiado para un lenguaje fuertemente tipado.
Personalmente, creo que la implementación de operadores aumentados terminó siendo un poco demasiado compleja en Python. Tiene muchos efectos secundarios sorprendentes, por ejemplo, este código:
t = ([42], [43])
t[0] += [44]
La segunda línea genera
TypeError: ''tuple'' object does not support item assignment
,
pero la operación se realiza con éxito de todos modos
:
t
será
([42, 44], [43])
después de ejecutar la línea que genera el error.
No son equivalentes:
b += (4,)
es la abreviatura de:
b.extend((4,))
while
+
concatena listas, entonces por:
b = b + (4,)
intentas concatenar una tupla en una lista