Python: ¿imprime una expresión generadora?

103

En el shell de Python, si ingreso una lista de comprensión como:

>>> [x for x in string.letters if x in [y for y in "BigMan on campus"]]

Obtengo un resultado muy bien impreso:

['a', 'c', 'g', 'i', 'm', 'n', 'o', 'p', 's', 'u', 'B', 'M']

Lo mismo para la comprensión de un diccionario:

>>> {x:x*2 for x in range(1,10)}
{1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18}

Si ingreso una expresión generadora, no obtengo una respuesta tan amigable:

>>> (x for x in string.letters if x in (y for y in "BigMan on campus"))
<generator object <genexpr> at 0x1004a0be0>

Sé que puedo hacerlo:

>>> for i in _: print i,
a c g i m n o p s u B M

Aparte de eso (o escribiendo una función auxiliar) ¿puedo evaluar e imprimir fácilmente ese objeto generador en el shell interactivo?

el lobo
fuente
2
¿Cuál es el verdadero problema aquí? ¿Que te falta?
Andreas Jung
3
@pynator: El "problema real" es solo que quiero poder imprimir el contenido de generator objectmientras construyo una comprensión interactivamente en el indicador interactivo. Llamar list(_)hace eso. Lo que he hecho es usar listas de comprensión y luego convertirlas en genexp en un código más grande. Estos pueden fallar en tiempo de ejecución de formas que no ocurre con las listas por comprensión.
el lobo
5
La respuesta corta es que una expresión generadora no se puede imprimir porque sus valores no existen; se generan bajo demanda. Lo que puede hacer (asumiendo que el generador se detiene en algún momento) es sacar todos los valores, como con list(), y luego imprimirlos.
Kos

Respuestas:

161

Respuesta rápida:

Hacer list()alrededor de una expresión generadora es (casi) exactamente equivalente a tener []corchetes alrededor. Así que sí, puedes hacer

>>> list((x for x in string.letters if x in (y for y in "BigMan on campus")))

Pero también puedes hacerlo

>>> [x for x in string.letters if x in (y for y in "BigMan on campus")]

Sí, eso convertirá la expresión del generador en una lista de comprensión. Es lo mismo y lista de llamadas () en él. Entonces, la forma de convertir una expresión generadora en una lista es ponerla entre corchetes.

Explicación detallada:

Una expresión generadora es una expresión "desnuda" for. Al igual que:

x*x for x in range(10)

Ahora, no puede pegar eso en una línea por sí solo, obtendrá un error de sintaxis. Pero puedes ponerlo entre paréntesis.

>>> (x*x for x in range(10))
<generator object <genexpr> at 0xb7485464>

Esto a veces se llama comprensión del generador, aunque creo que el nombre oficial sigue siendo expresión del generador, en realidad no hay ninguna diferencia, los paréntesis solo están ahí para que la sintaxis sea válida. No los necesita si lo pasa como el único parámetro a una función, por ejemplo:

>>> sorted(x*x for x in range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Básicamente, todas las demás comprensiones disponibles en Python 3 y Python 2.7 son solo azúcar sintáctica alrededor de una expresión generadora. Establecer comprensiones:

>>> {x*x for x in range(10)}
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}

>>> set(x*x for x in range(10))
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}

Dict comprensiones:

>>> dict((x, x*x) for x in range(10))
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

>>> {x: x*x for x in range(10)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

Y enumere las comprensiones en Python 3:

>>> list(x*x for x in range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> [x*x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

En Python 2, las listas por comprensión no son solo azúcar sintáctica. Pero la única diferencia es que x bajo Python 2 se filtrará en el espacio de nombres.

>>> x
9

Mientras esté bajo Python 3, obtendrá

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

Esto significa que la mejor manera de obtener una buena impresión del contenido de su expresión generadora en Python es hacer una lista de comprensión. Sin embargo, esto obviamente no funcionará si ya tiene un objeto generador. Hacer eso solo hará una lista de un generador:

>>> foo = (x*x for x in range(10))
>>> [foo]
[<generator object <genexpr> at 0xb7559504>]

En ese caso, deberá llamar a list():

>>> list(foo)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Aunque esto funciona, pero es un poco estúpido:

>>> [x for x in foo]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Lennart Regebro
fuente
5
El término oficial sigue siendo "expresión generadora" porque la palabra "comprensión" implica iteración, que es algo que no hace un genexp , como esta pregunta y respuesta ilustran muy bien :)
ncoghlan
2
list( generator-expression )no imprime la expresión del generador; está generando una lista (y luego imprimiéndola en un shell interactivo). En lugar de generar una lista, en Python 3, podría dividir la expresión del generador en una declaración de impresión. Es decir) print(*(generator-expression)). Esto imprime los elementos sin comas y sin corchetes al principio y al final.
AJNeufeld
17

A diferencia de una lista o un diccionario, un generador puede ser infinito. Hacer esto no funcionaría:

def gen():
    x = 0
    while True:
        yield x
        x += 1
g1 = gen()
list(g1)   # never ends

Además, leer un generador lo cambia, por lo que no hay una manera perfecta de verlo. Para ver una muestra de la salida del generador, puede hacer

g1 = gen()
[g1.next() for i in range(10)]
Chad
fuente
2
Votado debido a la afirmación de que un generador puede ser infinito, lo que provoca un bucle o una parada total (según sus especificaciones (risas)).
Milán Velebit
16

Puede simplemente envolver la expresión en una llamada a list:

>>> list(x for x in string.letters if x in (y for y in "BigMan on campus"))
['a', 'c', 'g', 'i', 'm', 'n', 'o', 'p', 's', 'u', 'B', 'M']
Björn Pollex
fuente
15

O siempre puede mapsobre un iterador, sin la necesidad de crear una lista intermedia:

>>> _ = map(sys.stdout.write, (x for x in string.letters if x in (y for y in "BigMan on campus")))
acgimnopsuBM
lbolla
fuente
3
esta es la única respuesta que realmente imprime el contenido del generador sin crear un objeto enorme.
Marek R
2
>>> list(x for x in string.letters if x in (y for y in "BigMan on campus"))
['a', 'c', 'g', 'i', 'm', 'n', 'o', 'p', 's', 'u', 'B', 'M']
Andreas Jung
fuente
En caso de que el generador sea infinito, provocará un bucle.
Milán Velebit