Pregunta Agregar a una lista definida en una tupla: ¿es un error? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Entonces tengo este código:

tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)

que genera este error:

TypeError: 'tuple' object does not support item assignment

Mientras este código:

tup = ([1,2,3],[7,8,9])
try:
    tup[0] += (4,5,6)
except TypeError:
    print tup

imprime esto:

([1, 2, 3, 4, 5, 6], [7, 8, 9])

¿Se espera este comportamiento?

Nota

Me doy cuenta de que este no es un caso de uso muy común. Sin embargo, aunque se espera el error, no esperaba que la lista cambiara.


32
2018-04-20 11:56


origen


Respuestas:


Sí, se espera

Una tupla no puede ser cambiada. Una tupla, como una lista, es una estructura que apunta a otros objetos. No le importa qué son esos objetos. Podrían ser cadenas, números, tuplas, listas u otros objetos.

Por lo tanto, hacer algo con uno de los objetos contenidos en la tupla, incluida la adición a ese objeto si es una lista, no es relevante para la semántica de la tupla.

(Imagínese si usted escribió una clase que tenía métodos que hacen que cambie su estado interno. No esperaría que sea imposible llamar a esos métodos en un objeto en función de dónde está almacenado).

U otro ejemplo:

>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)

Dos listas mutables referenciadas por una lista y por una tupla. Debería poder hacer la última línea (respuesta: sí). Si crees que la respuesta es no, ¿por qué no? Debería t cambiar la semántica de l3 (respuesta: no).

Si desea un objeto inmutable de estructuras secuenciales, debe ser tuplas todo el camino hacia abajo.

¿Por qué error?

Este ejemplo usa el operador infijo:

Muchas operaciones tienen una versión "in situ". Las siguientes funciones   proporcionar un acceso más primitivo a los operadores in situ que el habitual   la sintaxis lo hace; por ejemplo, el enunciado x + = y es equivalente a x =   operator.iadd (x, y) Otra forma de decirlo es decir que z =   operator.iadd (x, y) es equivalente a la sentencia compuesta z = x; z   + = y.

https://docs.python.org/2/library/operator.html

Así que esto:

l = [1, 2, 3]
tup = (l,)
tup[0] += (4,5,6)

es equivalente a esto:

l = [1, 2, 3]
tup = (l,)
x = tup[0]
x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None
tup[0] = x

los __iadd__ línea tiene éxito, y modifica la primera lista. Entonces la lista ha sido cambiada. los __iadd__ llamada devuelve la lista mutada.

La segunda línea intenta asignar la lista a la tupla, y esto falla.

Entonces, al final del programa, la lista se ha extendido pero la segunda parte del += operación fallida. Para los detalles, ver esta pregunta.


28
2018-04-20 11:58



Bueno, supongo tup[0] += (4, 5, 6) se traduce a:

tup[0] = tup[0].__iadd__((4,5,6))

tup[0].__iadd__((4,5,6)) se ejecuta normalmente cambiando la lista en el primer elemento. Pero la asignación falla ya que las tuplas son inmutables.


11
2018-04-20 12:07



Las tuplas no se pueden cambiar directamente, corregir. Sin embargo, puedes cambiar el elemento de una tupla por referencia. Me gusta:

>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])

5
2018-04-20 12:11



Los desarrolladores de Python escribieron una explicación oficial sobre por qué sucede aquí: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works

La versión corta es que + = en realidad hace dos cosas, una después de la otra:

  1. Ejecuta la cosa a la derecha.
  2. asigna el resultado a la variable de la izquierda

En este caso, el paso 1 funciona porque puede agregar cosas a las listas (son mutables), pero el paso 2 falla porque no puede poner cosas en tuplas después de crearlas (las tuplas son inmutables).

En un programa real, sugeriría que no hagas una cláusula try-except, porque tup[0].extend([4,5,6]) hace exactamente lo mismo.


1
2017-10-05 11:38