Agregue a una lista definida en una tupla: ¿es un error?

Así que tengo este código:

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

lo 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, mientras se espera el error, no esperaba que la lista cambiara.

Si se espera

Una tupla no se puede cambiar. Una tupla, como una lista, es una estructura que apunta a otros objetos. No importa qué son esos objetos. Pueden ser cadenas, números, tuplas, listas u otros objetos.

Por lo tanto, hacer cualquier cosa con uno de los objetos contenidos en la tupla, incluso agregarse a ese objeto si es una lista, no es relevante para la semántica de la tupla.

(Imagínese si escribiera una clase que tuviera métodos que hicieran cambiar su estado interno. No esperaría que fuera imposible llamar a esos métodos en un objeto según el lugar donde se almacena).

O 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. ¿Debo poder hacer la última línea (respuesta: sí). Si crees que la respuesta es no, ¿por qué no? Debería cambiar la semántica de l3 (respuesta: no).

Si desea un objeto inmutable de estructuras secuenciales, debe ser tuplas completamente hacia abajo.

¿Por qué error?

Este ejemplo usa el operador infijo:

Muchas operaciones tienen una versión “in situ”. Las siguientes funciones proporcionan un acceso más primitivo a los operadores en el lugar que la syntax habitual; por ejemplo, la statement 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 statement 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 

La línea __iadd__ tiene éxito y modifica la primera lista. Así que la lista ha sido cambiada. La llamada __iadd__ devuelve la lista mutada.

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

Entonces, al final del progtwig, la lista se ha extendido, pero la segunda parte de la operación += fallado. Para los detalles, vea esta pregunta .

Bueno, supongo que 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 tarea falla ya que las tuplas son inmutables.

Las tuplas no se pueden cambiar directamente, correcto. 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]) 

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 -la-adición-funciona

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

  1. Ejecutar la cosa a la derecha.
  2. Asigna el resultado a la variable de la izquierda.

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

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