¿Por qué no hay comprensión de tuplas en Python?


340

Como todos sabemos, hay una lista de comprensión, como

[i for i in [1, 2, 3, 4]]

y hay comprensión del diccionario, como

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

pero

(i for i in (1, 2, 3))

terminará en un generador, no en una tuplecomprensión. ¿Porqué es eso?

Supongo que a tuplees inmutable, pero esta no parece ser la respuesta.


16
También hay una comprensión de conjunto - que se parece mucho a una comprensión dict ...
mgilson

3
Hay un error de sintaxis en su código: {i:j for i,j in {1:'a', 2:'b'}}debería ser{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose

@InbarRose Gracias por señalarlo.-
Shady Xu

Solo por el bien de la posteridad, hay una discusión sobre esto en Python Chat
Inbar Rose

Respuestas:


471

Puedes usar una expresión generadora:

tuple(i for i in (1, 2, 3))

pero los paréntesis ya se tomaron para ... expresiones generadoras.


15
Por este argumento, podríamos decir una lista-comprensión es innecesaria demasiado: list(i for i in (1,2,3)). Realmente creo que es simplemente porque no hay una sintaxis limpia (o al menos nadie ha pensado en una)
mgilson

79
Una lista o una comprensión establecida o dictada es simplemente azúcar sintáctica para usar una expresión generadora que genera un tipo específico. list(i for i in (1, 2, 3))es una expresión generadora que genera una lista, set(i for i in (1, 2, 3))genera un conjunto. ¿Eso significa que la sintaxis de comprensión no es necesaria? Quizás no, pero es muy útil. Para los casos raros que necesita una tupla, la expresión del generador funcionará, es clara y no requiere la invención de otro paréntesis o paréntesis.
Martijn Pieters

16
La respuesta es obviamente porque la sintaxis y el paréntesis de la tupla son ambiguos
Charles Salvia

19
La diferencia entre usar una comprensión y usar un constructor + generador es más que sutil si te importa el rendimiento. Las comprensiones resultan en una construcción más rápida en comparación con el uso de un generador pasado a un constructor. En el último caso, está creando y ejecutando funciones y las funciones son caras en Python. [thing for thing in things]construye una lista mucho más rápida que list(thing for thing in things). Una comprensión de tuplas no sería inútil; tuple(thing for thing in things)tiene problemas de latencia y tuple([thing for thing in things])podría tener problemas de memoria.
Justin Turner Arthur

99
@MartijnPieters, ¿puedes potencialmente reformular A list or set or dict comprehension is just syntactic sugar to use a generator expression? Está causando confusión al ver a las personas como un medio equivalente para un fin. No es azúcar técnicamente sintáctico ya que los procesos son realmente diferentes, incluso si el producto final es el mismo.
jpp

77

Raymond Hettinger (uno de los desarrolladores principales de Python) dijo lo siguiente sobre las tuplas en un tweet reciente :

#python tip: Generalmente, las listas son para bucles; tuplas para estructuras. Las listas son homogéneas; tuplas heterogéneas. Listas de longitud variable.

Esto (para mí) respalda la idea de que si los elementos en una secuencia están lo suficientemente relacionados como para ser generados por un generador, bueno, entonces debería ser una lista. Aunque una tupla es iterable y parece simplemente una lista inmutable, es realmente el equivalente de Python de una estructura C:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

se convierte en Python

x = (3, 'g', 5.9)

26
Sin embargo, la propiedad de inmutibilidad puede ser importante y, a menudo, una buena razón para usar una tupla cuando normalmente usaría una lista. Por ejemplo, si tiene una lista de 5 números que desea usar como clave para un dict, entonces tupla es el camino a seguir.
Pavón

Es un buen consejo de Raymond Hettinger. Todavía diría que hay un caso de uso para usar el constructor de tuplas con un generador, como desempaquetar otra estructura, quizás más grande, en una más pequeña iterando sobre los atributos que está interesado en convertir a un registro de tupla.
Dave

2
@dave Probablemente puedas usarlo operator.itemgetteren ese caso.
chepner 01 de

@chepner, ya veo. Eso está bastante cerca de lo que quiero decir. Devuelve un invocable, por lo que si solo necesito hacerlo una vez, no veo mucha victoria frente a solo usarlo tuple(obj[item] for item in items)directamente. En mi caso, estaba incrustando esto en una lista de comprensión para hacer una lista de registros de tuplas. Si necesito hacer esto repetidamente en todo el código, itemgetter se ve muy bien. ¿Tal vez el itemgetter sería más idiomático de cualquier manera?
Dave

Veo la relación entre frozenset y set de forma análoga a la de tuple y list. Se trata menos de la heterogeneidad y más de la inmutabilidad: los congelados y las tuplas pueden ser claves para los diccionarios, las listas y los conjuntos no pueden debido a su mutabilidad.
políglota

56

Desde Python 3.5 , también puede usar la *sintaxis de desempaquetado splat para desempaquetar una expresión de generador:

*(x for x in range(10)),

2
Esto es genial (y funciona), ¡pero no puedo encontrar ningún lugar donde esté documentado! ¿Tienes un enlace?
felixphew

8
Nota: Como detalle de implementación, esto es básicamente lo mismo que hacer tuple(list(x for x in range(10)))( las rutas de código son idénticas , y ambas listcrean un, con la única diferencia de que el paso final es crear un tupledesde listy desechar el listcuando una tuplesalida es requerido). Significa que en realidad no evitas un par de temporarios.
ShadowRanger

44
Para ampliar el comentario de @ShadowRanger, aquí hay una pregunta en la que muestran que la sintaxis literal splat + tuple es en realidad bastante más lenta que pasar una expresión de generador al constructor de tuplas.
Lucubrator

Estoy intentando esto en Python 3.7.3 y *(x for x in range(10))no funciona. Consigo SyntaxError: can't use starred expression here. Sin embargo tuple(x for x in range(10))funciona.
Ryan H.

44
@RyanH. necesitas poner una coma al final.
czheo

27

Como se macmmenciona en otro póster , la forma más rápida de crear una tupla a partir de un generador es tuple([generator]).


Comparación de rendimiento

  • Lista de comprensión:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tupla de la comprensión de la lista:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tupla del generador:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Tupla de desembalaje:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Mi versión de python :

$ python3 --version
Python 3.6.3

Por lo tanto, siempre debe crear una tupla a partir de una comprensión de la lista a menos que el rendimiento no sea un problema.


10
Nota: tuplede listcomp requiere un uso de memoria máximo basado en el tamaño combinado de la final tupley list. tuplede un genexpr, aunque más lento, significa que solo paga por el final tuple, no temporal list(el genexpr en sí ocupa una memoria aproximadamente fija). Por lo general, no tiene sentido, pero puede ser importante cuando los tamaños involucrados son enormes.
ShadowRanger

25

La comprensión funciona haciendo un bucle o iterando sobre los elementos y asignándolos a un contenedor, una Tupla no puede recibir asignaciones.

Una vez que se crea una Tupla, no se puede agregar, extender o asignar. La única forma de modificar una Tupla es si uno de sus objetos puede asignarse a sí mismo (es un contenedor sin tupla). Porque la Tupla solo tiene una referencia a ese tipo de objeto.

Además, una tupla tiene su propio constructor tuple()que puede asignar a cualquier iterador. Lo que significa que para crear una tupla, podrías hacer:

tuple(i for i in (1,2,3))

99
De alguna manera, estoy de acuerdo (sobre que no es necesario porque una lista servirá), pero en otras formas no estoy de acuerdo (sobre el razonamiento porque es inmutable). De alguna manera, tiene más sentido tener una comprensión de los objetos inmutables. que hace lst = [x for x in ...]; x.append()?
mgilson

@mgilson No estoy seguro de cómo se relaciona eso con lo que dije.
Inbar Rose

2
@mgilson si una tupla es inmutable, eso significa que la implementación subyacente no puede "generar" una tupla ("generación" implica construir una pieza a la vez). inmutable significa que no puedes construir el que tiene 4 piezas alterando el que tiene 3 piezas. en su lugar, implementa la "generación" de tuplas creando una lista, algo diseñado para la generación, luego construye la tupla como último paso y descarta la lista. El lenguaje refleja esta realidad. Piense en las tuplas como estructuras de C.
Scott

2
aunque sería razonable que el azúcar sintáctico de las comprensiones funcione para las tuplas, ya que no puede usar la tupla hasta que se devuelva la comprensión. Efectivamente, no actúa como mutable, más bien una comprensión de tupla podría comportarse de manera muy parecida a la adición de cadenas.
uchuugaka

12

Mi mejor conjetura es que se quedaron sin paréntesis y no pensaron que sería lo suficientemente útil como para justificar la adición de una sintaxis "fea" ...


1
Soportes angulares sin usar.
uchuugaka

@uchuugaka: no del todo. Se utilizan para operadores de comparación. Probablemente todavía podría hacerse sin ambigüedad, pero tal vez no valga la pena el esfuerzo ...
mgilson

3
@uchuugaka Vale la pena señalar que {*()}, aunque feo, ¡funciona como un conjunto vacío literal!
MI Wright

1
Ugh Desde un punto de vista estético, creo que soy parcial para set():)
mgilson

1
@QuantumMechanic: Sí, ese es el punto; las generalizaciones de desempaquetado hicieron posible el "conjunto literal" vacío. Tenga en cuenta que {*[]}es estrictamente inferior a las otras opciones; la cadena vacía y vacía tuple, siendo inmutables, son singletons, por lo que no se necesita temporal para construir el vacío set. Por el contrario, el vacío listno es un singleton, por lo que realmente tiene que construirlo, usarlo para construirlo sety luego destruirlo, perdiendo cualquier ventaja de rendimiento trivial que proporciona el operador mono mono.
ShadowRanger

8

Las tuplas no se pueden agregar eficientemente como una lista.

Por lo tanto, una comprensión de tupla necesitaría usar una lista internamente y luego convertirla en una tupla.

Eso sería lo mismo que haces ahora: tupla ([comprensión])


3

Los paréntesis no crean una tupla. aka one = (two) no es una tupla. La única forma de hacerlo es uno = (dos,) o uno = tupla (dos). Entonces una solución es:

tuple(i for i in myothertupleorlistordict) 

bonito. Es casi lo mismo.
uchuugaka

-1

Creo que es simplemente por razones de claridad, no queremos saturar el lenguaje con demasiados símbolos diferentes. Además, una tuplecomprensión nunca es necesaria , una lista solo se puede usar en cambio con diferencias de velocidad insignificantes, a diferencia de una comprensión dictada en lugar de una comprensión de lista.


-2

Podemos generar tuplas a partir de una lista de comprensión. El siguiente agrega dos números secuencialmente en una tupla y da una lista de los números 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.