¿Por qué necesitamos tuplas en Python (o cualquier tipo de datos inmutable)?

140

He leído varios tutoriales de Python (Sumérgete en Python, por ejemplo) y la referencia del lenguaje en Python.org: no veo por qué el lenguaje necesita tuplas.

Las tuplas no tienen métodos comparados con una lista o conjunto, y si debo convertir una tupla en un conjunto o lista para poder ordenarlos, ¿cuál es el punto de usar una tupla en primer lugar?

¿Inmutabilidad?

¿Por qué a alguien le importa si una variable vive en un lugar diferente en la memoria que cuando se asignó originalmente? Todo este negocio de inmutabilidad en Python parece estar demasiado enfatizado.

En C / C ++ si asigno un puntero y apunto a alguna memoria válida, no me importa dónde se encuentra la dirección, siempre que no sea nula antes de usarla.

Cada vez que hago referencia a esa variable, no necesito saber si el puntero sigue apuntando a la dirección original o no. Solo busco nulo y lo uso (o no).

En Python, cuando asigno una cadena (o tupla), la asigno a x, luego modifico la cadena, ¿por qué me importa si es el objeto original? Mientras la variable apunte a mis datos, eso es todo lo que importa.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x Todavía hace referencia a los datos que quiero, ¿por qué alguien debe preocuparse si su identificación es la misma o diferente?

pyNewGuy
fuente
12
estás prestando atención al aspecto incorrecto de la mutabilidad: "si la identificación es la misma o diferente" es solo un efecto secundario; "si los datos señalados por otras referencias que anteriormente apuntaban al mismo objeto ahora reflejan actualizaciones" es crítico.
Charles Duffy

Respuestas:

124
  1. los objetos inmutables pueden permitir una optimización sustancial; presumiblemente, esta es la razón por la cual las cadenas también son inmutables en Java, desarrolladas por separado pero casi al mismo tiempo que Python, y casi todo es inmutable en lenguajes verdaderamente funcionales.

  2. en Python en particular, solo los inmutables pueden ser hashables (y, por lo tanto, miembros de conjuntos o claves en los diccionarios). Nuevamente, esto permite la optimización, pero es mucho más que simplemente "sustancial" (diseñar tablas hash decentes que almacenen objetos completamente mutables es una pesadilla, ya sea que tomes copias de todo tan pronto como lo hagas, o la pesadilla de verificar si el hash del objeto ha cambiado desde la última vez que hizo referencia a que levanta su fea cabeza).

Ejemplo de problema de optimización:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop
Alex Martelli
fuente
11
@musicfreak, mira la edición que acabo de hacer donde construir una tupla es más de 7,6 veces más rápido que crear la lista equivalente: ahora no puedes decir que ya "nunca has visto una diferencia notable", a menos que tu definición de "notable" "es realmente peculiar ...
Alex Martelli
11
@musicfreak Creo que estás haciendo un mal uso "La optimización prematura es la raíz de todo mal". Hay una gran diferencia entre hacer una optimización prematura en una aplicación (por ejemplo, decir "las tuplas son más rápidas que las listas, ¡así que solo usaremos tuplas en toda la aplicación!") Y hacer puntos de referencia. El punto de referencia de Alex es perspicaz y saber que construir una tupla es más rápido que crear una lista podría ayudarnos en futuras operaciones de optimización (cuando realmente se necesita).
Virgil Dupras
55
@Alex, ¿"construir" una tupla es realmente más rápido que "construir una lista", o estamos viendo el resultado del tiempo de ejecución de Python almacenando en caché la tupla? Me parece lo último.
Tríptico
66
@ACoolie, eso está totalmente dominado por las randomllamadas (¡intenta hacer eso, ya lo verás!), Así que no es muy significativo. Pruebe python -mtimeit -s "x=23" "[x,x]"y verá una aceleración más significativa de 2-3 veces para construir la tupla frente a la construcción de la lista.
Alex Martelli
9
para cualquiera que se pregunte: pudimos ahorrar más de una hora de procesamiento de datos al cambiar de listas a tuplas.
Mark Ribau
42

Ninguna de las respuestas anteriores señala el problema real de las tuplas frente a las listas, que muchos nuevos en Python parecen no comprender completamente.

Las tuplas y las listas tienen diferentes propósitos. Las listas almacenan datos homogéneos. Puede y debe tener una lista como esta:

["Bob", "Joe", "John", "Sam"]

La razón por la cual es un uso correcto de las listas es porque esos son todos tipos de datos homogéneos, específicamente, los nombres de las personas. Pero toma una lista como esta:

["Billy", "Bob", "Joe", 42]

Esa lista es el nombre completo de una persona y su edad. Ese no es un tipo de datos. La forma correcta de almacenar esa información es en una tupla o en un objeto. Digamos que tenemos algunos:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

La inmutabilidad y la mutabilidad de las tuplas y las listas no es la principal diferencia. Una lista es una lista del mismo tipo de elementos: archivos, nombres, objetos. Las tuplas son una agrupación de diferentes tipos de objetos. Tienen diferentes usos, y muchos codificadores de Python abusan de las listas para lo que están destinadas las tuplas.

Por favor no


Editar:

Creo que esta publicación de blog explica por qué pienso esto mejor que yo: http://news.e-scribe.com/397

Grant Paul
fuente
13
Creo que tienes una visión que al menos no estoy de acuerdo conmigo, no conozco a los demás.
Stefano Borini
13
También estoy totalmente en desacuerdo con esta respuesta. La homogeneidad de los datos no tiene absolutamente nada que ver con si debe usar una lista o una tupla. Nada en Python sugiere esta distinción.
Glenn Maynard
14
Guido también hizo este punto hace unos años. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy
11
Aunque Guido (el diseñador de Python) pretendía que las listas se usaran para datos homogéneos y las tuplas para heterogéneos, el hecho es que el lenguaje no impone esto. Por lo tanto, creo que esta interpretación es más una cuestión de estilo que otra cosa. Sucede que en los casos de uso típicos de muchas personas, las listas tienden a ser de tipo matriz y las tuplas tienden a ser de tipo registro. Pero esto no debería impedir que la gente use listas para datos heterogéneos si se adapta mejor a su problema. Como dice el Zen de Python: la practicidad vence a la pureza.
John Y
9
@ Glenn, básicamente estás equivocado. Uno de los principales usos de las tuplas es como un tipo de datos compuesto para almacenar múltiples datos relacionados. El hecho de que pueda iterar sobre una tupla y realizar muchas de las mismas operaciones no cambia esto. (Como referencia considere que las tuplas en muchos otros idiomas no tienen las mismas características iterables que sus contrapartes de la lista)
HS.
22

si debo convertir una tupla en un conjunto o lista para poder ordenarlos, ¿cuál es el punto de usar una tupla en primer lugar?

En este caso particular, probablemente no haya un punto. Esto no es un problema, porque este no es uno de los casos en los que consideraría usar una tupla.

Como señalas, las tuplas son inmutables. Las razones para tener tipos inmutables se aplican a las tuplas:

  • eficiencia de copia: en lugar de copiar un objeto inmutable, puede usar un alias (vincular una variable a una referencia)
  • eficiencia de comparación: cuando utiliza la copia por referencia, puede comparar dos variables comparando la ubicación, en lugar del contenido
  • Interno: necesita almacenar como máximo una copia de cualquier valor inmutable
  • no hay necesidad de sincronizar el acceso a objetos inmutables en código concurrente
  • corrección const: algunos valores no deberían poder cambiar. Esto (para mí) es la razón principal de los tipos inmutables.

Tenga en cuenta que una implementación particular de Python puede no hacer uso de todas las características anteriores.

Las claves del diccionario deben ser inmutables; de lo contrario, cambiar las propiedades de un objeto clave puede invalidar invariantes de la estructura de datos subyacente. Las tuplas se pueden usar como claves. Esto es una consecuencia de la corrección constante.

Consulte también " Introducción de tuplas ", de Dive Into Python .

outis
fuente
2
id ((1,2,3)) == id ((1,2,3)) es falso. No puede comparar tuplas simplemente comparando la ubicación, porque no hay garantía de que se hayan copiado por referencia.
Glenn Maynard
@Glenn: Tenga en cuenta el comentario de calificación "cuando esté utilizando copia por referencia". Si bien el codificador puede crear su propia implementación, la copia por referencia para las tuplas es en gran medida una cuestión del intérprete / compilador. Me refería principalmente a cómo ==se implementa a nivel de plataforma.
outis
1
@Glenn: también tenga en cuenta que la copia por referencia no se aplica a las tuplas en (1,2,3) == (1,2,3). Eso es más una cuestión de internado.
outis
Como dije claramente, no hay garantía de que se hayan copiado por referencia . Las tuplas no están internadas en Python; Ese es un concepto de cuerda.
Glenn Maynard
Como dije muy claramente: no estoy hablando del programador que compara tuplas comparando ubicación. Estoy hablando de la posibilidad de que la plataforma pueda, lo que puede garantizar una copia por referencia. Además, la internación se puede aplicar a cualquier tipo inmutable, no solo a cadenas. La implementación principal de Python puede no incluir tipos inmutables, pero el hecho de que Python tenga tipos inmutables hace que la internación sea una opción.
outis 01 de
15

A veces nos gusta usar objetos como teclas de diccionario

Por lo que vale, las tuplas crecieron recientemente (2.6+) index()y los count()métodos

John La Rooy
fuente
55
+1: una lista mutable (o conjunto mutable o diccionario mutable) como clave de diccionario no puede funcionar. Así que necesitamos listas inmutables ("tuplas"), conjuntos congelados y ... bueno ... un diccionario congelado, supongo.
S.Lott
9

Siempre he encontrado que tener dos tipos completamente separados para la misma estructura de datos básica (matrices) es un diseño incómodo, pero no un problema real en la práctica. (Cada idioma tiene sus verrugas, Python incluido, pero este no es importante).

¿Por qué a alguien le importa si una variable vive en un lugar diferente en la memoria que cuando se asignó originalmente? Todo este negocio de inmutabilidad en Python parece estar demasiado enfatizado.

Estas son cosas diferentes. La mutabilidad no está relacionada con el lugar donde se almacena en la memoria; significa que las cosas que señala no pueden cambiar.

Los objetos de Python no pueden cambiar de ubicación después de que se crean, mutables o no. (Más exactamente, el valor de id () no puede cambiar; lo mismo, en la práctica). El almacenamiento interno de objetos mutables puede cambiar, pero ese es un detalle de implementación oculto.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

Esto no está modificando ("mutando") la variable; está creando una nueva variable con el mismo nombre y descartando la anterior. Compárese con una operación mutante:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

Como otros han señalado, esto permite usar matrices como claves para diccionarios y otras estructuras de datos que necesitan inmutabilidad.

Tenga en cuenta que las claves para los diccionarios no tienen que ser completamente inmutables. Solo la parte que se usa como clave debe ser inmutable; Para algunos usos, esta es una distinción importante. Por ejemplo, podría tener una clase que represente a un usuario, que compara la igualdad y un hash por el nombre de usuario único. Luego podría colgar otros datos mutables en la clase: "el usuario ha iniciado sesión", etc. Dado que esto no afecta la igualdad o el hash, es posible y perfectamente válido usar esto como clave en un diccionario. Esto no se necesita con demasiada frecuencia en Python; Solo lo señalo, ya que varias personas han afirmado que las claves deben ser "inmutables", lo cual es solo parcialmente correcto. Sin embargo, he usado esto muchas veces con mapas y conjuntos de C ++.

Glenn Maynard
fuente
>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Usted ' acabo de modificar un tipo de datos mutables, por lo que no tiene sentido relacionado con la pregunta original. x = "hola" Identificación (x) 12345 x = 'adiós' Identificación (x) 65432 A quién le importa si se trata de un objeto nuevo o no tan largo como x puntos a los datos que he asignado, que es lo que importa..
pyNewGuy
44
Estás confundido más allá de mi capacidad de ayudarte.
Glenn Maynard
+1 por señalar la confusión en las subpreguntas, que parecen ser la principal fuente de dificultad para percibir el valor de las tuplas.
outis
1
Si pudiera, otro +1 para señalar que la verdadera rúbrica para las claves es si el objeto es hashable o no ( docs.python.org/glossary.html#term-hashable ).
outis
7

Como gnibbler ofreció en un comentario, Guido tenía una opinión que no es totalmente aceptada / apreciada: "las listas son para datos homogéneos, las tuplas son para datos heterogéneos". Por supuesto, muchos de los opositores interpretaron que esto significa que todos los elementos de una lista deberían ser del mismo tipo.

Me gusta verlo de manera diferente, no muy diferente de lo que otros han tenido en el pasado:

blue= 0, 0, 255
alist= ["red", "green", blue]

Tenga en cuenta que considero que alist es homogéneo, incluso si type (alist [1])! = Type (alist [2]).

Si puedo cambiar el orden de los elementos y no tendré problemas en mi código (aparte de los supuestos, por ejemplo, "debería estar ordenado"), entonces debería usarse una lista. Si no (como en la tupla blueanterior), entonces debería usar una tupla.

tzot
fuente
Si pudiera, votaría esta respuesta 15 veces. Esto es exactamente lo que siento por las tuplas.
Grant Paul
6

Son importantes ya que garantizan a la persona que llama que el objeto que pasa no será mutado. Si haces esto:

a = [1,1,1]
doWork(a)

La persona que llama no tiene garantía del valor de un después de la llamada. Sin embargo,

a = (1,1,1)
doWorK(a)

Ahora, como llamante o como lector de este código, sabe que a es lo mismo. Para este escenario, siempre podría hacer una copia de la lista y pasarla, pero ahora está desperdiciando ciclos en lugar de usar una construcción de lenguaje que tenga más sentido semántico.

Matthew Manela
fuente
1
Esta es una propiedad muy secundaria de las tuplas. Hay demasiados casos en los que tiene un objeto mutable que desea pasar a una función y no tiene que modificarlo, ya sea una lista preexistente o alguna otra clase. Simplemente no hay un concepto de "parámetros const por referencia" en Python (por ejemplo, const foo y en C ++). Las tuplas te dan esto si resulta conveniente usar una tupla, pero si has recibido una lista de tu interlocutor, ¿realmente la convertirás en una tupla antes de pasarla a otro lugar?
Glenn Maynard
Estoy de acuerdo contigo en eso. Una tupla no es lo mismo que abofetear a una palabra clave constante. Mi punto es que la inmutabilidad de una tupla tiene un significado adicional para el lector del código. Dada una situación en la que ambos funcionarían y su expectativa es que no debería cambiar el uso de la tupla agregará ese significado adicional para el lector (al tiempo que lo garantiza)
Matthew Manela
a = [1,1,1] doWork (a) si dowork () se define como def dowork (arg): arg = [0,0,0] llamar a dowork () en una lista o tupla tiene el mismo resultado
pyNewGuy
1

puedes ver aquí para una discusión sobre esto

ghostdog74
fuente
1

Su pregunta (y comentarios de seguimiento) se centran en si el id () cambia durante una tarea. Centrarse en este efecto de continuación de la diferencia entre el reemplazo de objetos inmutables y la modificación de objetos mutables en lugar de la diferencia en sí misma quizás no sea el mejor enfoque.

Antes de continuar, asegúrese de que el comportamiento que se muestra a continuación sea el que espera de Python.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

En este caso, se cambió el contenido de a2, aunque solo a1 tenía un nuevo valor asignado. Contraste a lo siguiente:

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

En este último caso, reemplazamos la lista completa, en lugar de actualizar su contenido. Con tipos inmutables como las tuplas, este es el único comportamiento permitido.

¿Por qué importa esto? Digamos que tienes un dict:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

Usando una tupla, el diccionario está a salvo de que sus claves se cambien "por debajo de él" a elementos que se combinan con un valor diferente. Esto es crítico para permitir una implementación eficiente.

Charles Duffy
fuente
Como otros han señalado, ¡inmutabilidad! = Hashability. No todas las tuplas se pueden usar como claves de diccionario: {([1], [2]): 'value'} falla porque las listas mutables en la tupla se pueden alterar, pero {((1), (2)): ' valor '} está bien.
Ned Deily
Ned, eso es cierto, pero no estoy seguro de que la distinción esté relacionada con la pregunta que se hace.
Charles Duffy
@ K.Nicholas, la edición que aprobó aquí cambió el código de tal manera que asignaba un número entero, no una tupla, haciendo que las operaciones de índice posteriores fallaran, por lo que no podrían haber probado que el nuevo la transcripción fue realmente posible. Problema correctamente identificado, claro; solución inválida
Charles Duffy
@MichaelPuckettII, del mismo modo, ver arriba.
Charles Duffy