¿Por qué las tuplas pueden contener elementos mutables?

178

Si una tupla es inmutable, ¿por qué puede contener elementos mutables?

Aparentemente, es una contradicción que cuando un elemento mutable, como una lista, se modifica, la tupla a la que pertenece sigue siendo inmutable.

qazwsx
fuente

Respuestas:

205

Esa es una excelente pregunta.

La idea clave es que las tuplas no tienen forma de saber si los objetos dentro de ellas son mutables. Lo único que hace que un objeto sea mutable es tener un método que altere sus datos. En general, no hay forma de detectar esto.

Otra idea es que los contenedores de Python en realidad no contienen nada. En cambio, mantienen referencias a otros objetos. Del mismo modo, las variables de Python no son como variables en lenguajes compilados; en cambio, los nombres de las variables son solo claves en un diccionario de espacio de nombres donde están asociados con un objeto correspondiente. Ned Batchhelder explica esto muy bien en su publicación de blog . De cualquier manera, los objetos solo conocen su recuento de referencia; no saben cuáles son esas referencias (variables, contenedores o elementos internos de Python).

Juntas, estas dos ideas explican su misterio (por qué una tupla inmutable que "contiene" una lista parece cambiar cuando cambia la lista subyacente). De hecho, la tupla no cambió (todavía tiene las mismas referencias a otros objetos que antes). La tupla no pudo cambiar (porque no tenía métodos de mutación). Cuando la lista cambió, la tupla no fue notificada del cambio (la lista no sabe si una variable, una tupla u otra lista hace referencia a ella).

Mientras estamos en el tema, aquí hay algunas otras ideas para ayudarlo a completar su modelo mental de lo que son las tuplas, cómo funcionan y su uso previsto:

  1. Las tuplas se caracterizan menos por su inmutabilidad y más por su propósito previsto.
    Las tuplas son la forma en que Python recopila datos heterogéneos bajo un mismo techo. Por ejemplo, s = ('www.python.org', 80) reúne una cadena y un número para que el par host / puerto pueda pasarse como un socket, un objeto compuesto. Visto desde esa perspectiva, es perfectamente razonable tener componentes mutables.

  2. La inmutabilidad va de la mano con otra propiedad, la hashability . Pero la hashability no es una propiedad absoluta. Si uno de los componentes de la tupla no es hashaable, entonces la tupla general tampoco lo es. Por ejemplo, t = ('red', [10, 20, 30])no es hashable.

El último ejemplo muestra una tupla de 2 que contiene una cadena y una lista. La tupla en sí no es mutable (es decir, no tiene ningún método para cambiar su contenido). Del mismo modo, la cadena es inmutable porque las cadenas no tienen ningún método de mutación. El objeto de lista tiene métodos de mutación, por lo que se puede cambiar. Esto muestra que la mutabilidad es una propiedad de un tipo de objeto: algunos objetos tienen métodos de mutación y otros no. Esto no cambia solo porque los objetos están anidados.

Recuerda dos cosas. Primero, la inmutabilidad no es mágica, es simplemente la ausencia de métodos de mutación. En segundo lugar, los objetos no saben qué variables o contenedores se refieren a ellos, solo conocen el recuento de referencias.

Espero que esto te haya sido útil :-)

Raymond Hettinger
fuente
No está mal "las tuplas no tienen forma de saber si los objetos dentro de ellas son mutables". Podemos detectar si la referencia implementa un método hash, entonces es inmutable. Como lo haría un dic o set. ¿No sería esto más una decisión de diseño para lo que las tuplas deben ser?
garg10may
@ garg10may 1) Hashibility no es fácil de detectar sin llamar hash()porque todo lo que hereda de object () es hashable y, por lo tanto, las subclases deben desactivar explícitamente el hash. 2) Hashibility no garantiza la inmutabilidad: es fácil hacer ejemplos de objetos hashables que son mutables. 3) Las tuplas, como la mayoría de los contenedores en Python, solo tienen referencias al objeto subyacente: no tienen la obligación de inspeccionarlas y hacer inferencias sobre ellas.
Raymond Hettinger
171

Eso es porque las tuplas no contienen listas, cadenas o números. Contienen referencias a otros objetos . 1 La imposibilidad de cambiar la secuencia de referencias que contiene una tupla no significa que no pueda mutar los objetos asociados con esas referencias. 2

1. Objetos, valores y tipos (ver: penúltimo párrafo)
2. La jerarquía de tipos estándar (ver: "Secuencias inmutables")

Ignacio Vazquez-Abrams
fuente
8
Hay una ambigüedad en la pregunta. Esta respuesta explica completamente por qué es posible que las tuplas contengan objetos mutables. No explica por qué las tuplas fueron diseñadas para contener objetos mutables. Creo que esta última es la pregunta más pertinente.
senderle
16

En primer lugar, la palabra "inmutable" puede significar muchas cosas diferentes para diferentes personas. Me gusta especialmente cómo Eric Lippert clasificó la inmutabilidad en su blog . Allí, enumera este tipo de inmutabilidad:

  • Inmutabilidad realio-trulio
  • Inmutabilidad de escritura única
  • Inmutabilidad de paletas
  • Inmutabilidad superficial vs profunda
  • Fachadas inmutables
  • Inmutabilidad observacional

Estos se pueden combinar de varias maneras para hacer aún más tipos de inmutabilidad, y estoy seguro de que existen más. El tipo de inmutabilidad que parece interesado en la inmutabilidad profunda (también conocida como transitiva), en la que los objetos inmutables solo pueden contener otros objetos inmutables.

El punto clave de esto es que la inmutabilidad profunda es solo uno de los muchos tipos de inmutabilidad. Puede adoptar el tipo que prefiera, siempre que sepa que su noción de "inmutable" probablemente difiere de la noción de otra persona de "inmutable".

Ken Wayne VanderLinde
fuente
¿Qué tipo de inmutabilidad tienen las tuplas de Python?
qazwsx
3
Las tuplas de Python tienen una inmutabilidad superficial (también conocida como no transitiva).
Ken Wayne VanderLinde
16

Según tengo entendido, esta pregunta debe reformularse como una pregunta sobre las decisiones de diseño: ¿Por qué los diseñadores de Python eligieron crear un tipo de secuencia inmutable que pueda contener objetos mutables?

Para responder a esta pregunta, tenemos que pensar en el propósito tuplas sirven: sirven como rápida , de uso general secuencias. Con eso en mente, resulta bastante obvio por qué las tuplas son inmutables pero pueden contener objetos mutables. Esto es:

  1. Las tuplas son rápidas y eficientes en cuanto a memoria: las tuplas son más rápidas de crear que las listas porque son inmutables. La inmutabilidad significa que las tuplas pueden crearse como constantes y cargarse como tales, utilizando el plegado constante . También significa que son más rápidos y más eficientes en la creación de memoria porque no hay necesidad de sobreasignación, etc. Son un poco más lentos que las listas para el acceso aleatorio a los elementos, pero nuevamente más rápido para desempacar (al menos en mi máquina). Si las tuplas fueran mutables, entonces no serían tan rápidas para propósitos como estos.

  2. Las tuplas son de uso general : las tuplas deben poder contener cualquier tipo de objeto. Están acostumbrados a (rápidamente) hacer cosas como listas de argumentos de longitud variable (a través del *operador en las definiciones de funciones). Si las tuplas no pudieran contener objetos mutables, serían inútiles para cosas como esta. Python tendría que usar listas, lo que probablemente ralentizaría las cosas, y ciertamente sería menos eficiente en la memoria.

Entonces, para cumplir su propósito, las tuplas deben ser inmutables, pero también deben poder contener objetos mutables. Si los diseñadores de Python quisieran crear un objeto inmutable que garantice que todos los objetos que "contiene" también sean inmutables, tendrían que crear un tercer tipo de secuencia. La ganancia no vale la complejidad adicional.

senderle
fuente
12

No puede cambiar el idde sus artículos. Por lo tanto, siempre contendrá los mismos elementos.

$ python
>>> t = (1, [2, 3])
>>> id(t[1])
12371368
>>> t[1].append(4)
>>> id(t[1])
12371368
kev
fuente
Esta es la demostración más apropiada de los ejemplos anteriores. Tuple tiene referencias a esos objetos que no cambian, aunque tener como máximo un componente mutable hace que toda la tupla sea inquebrantable.
piepi
5

Me arriesgaré aquí y diré que la parte relevante aquí es que si bien puedes cambiar el contenido de una lista, o el estado de un objeto, contenido dentro de una tupla, lo que no puedes cambiar es que el objeto o la lista está ahí. Si tenía algo que dependía de que la cosa [3] fuera una lista, incluso si estaba vacía, entonces podría ver que esto es útil.


fuente
3

Una razón es que no hay una forma general en Python para convertir un tipo mutable en uno inmutable (vea el PEP 351 rechazado y la discusión vinculada de por qué fue rechazado). Por lo tanto, sería imposible poner varios tipos de objetos en tuplas si tuviera esta restricción, incluyendo casi cualquier objeto no hashable creado por el usuario.

La única razón por la que los diccionarios y los conjuntos tienen esta restricción es que requieren que los objetos sean hash, ya que se implementan internamente como tablas hash. Pero tenga en cuenta que, irónicamente, los diccionarios y los conjuntos en sí no son inmutables (o hashaable). Las tuplas no usan el hash de un objeto, por lo que su mutabilidad no importa.

asmeurer
fuente
2

Una tupla es inmutable en el sentido de que la tupla en sí no puede expandirse o encogerse, no que todos los elementos contenidos en sí mismos sean inmutables. De lo contrario, las tuplas son aburridas.

Adam Smith
fuente