Python: imprimir uma expressão geradora?

103

No shell Python, se eu inserir uma compreensão de lista, como:

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

Obtenho um resultado bem impresso:

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

O mesmo para uma compreensão de dicionário:

>>> {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}

Se eu inserir uma expressão geradora, não recebo uma resposta tão amigável:

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

Eu sei que posso fazer isso:

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

Além disso (ou escrevendo uma função auxiliar), posso avaliar e imprimir facilmente esse objeto gerador no shell interativo?

o lobo
fonte
2
Qual é o verdadeiro problema aqui? O que você está perdendo?
Andreas Jung,
3
@pynator: O "problema real" é apenas que eu quero ser capaz de imprimir o conteúdo generator objectenquanto construo interativamente uma compreensão no prompt interativo. Ligar list(_)faz isso. O que eu fiz foi usar compreensões de lista e transformá-las em genexp em código maior. Eles podem falhar em tempo de execução de maneiras que as compreensões de lista não.
o lobo
5
A resposta curta é que uma expressão geradora não pode ser impressa porque seus valores não existem; eles são gerados sob demanda. O que você pode fazer (supondo que o gerador pare em algum momento) é obter todos os valores dele, como com list(), e imprimi-los.
Kos

Respostas:

161

Resposta rápida:

Fazer em list()torno de uma expressão geradora é (quase) exatamente equivalente a ter []colchetes ao redor dela. Então sim, você pode fazer

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

Mas você também pode fazer

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

Sim, isso transformará a expressão do gerador em uma compreensão de lista. É a mesma coisa e lista de chamadas () nela. Portanto, a maneira de transformar uma expressão geradora em uma lista é colocá-la entre colchetes.

Explicação detalhada:

Uma expressão geradora é uma expressão "nua" for. Igual a:

x*x for x in range(10)

Agora, você não pode colocar isso em uma linha por si só, você obterá um erro de sintaxe. Mas você pode colocar parênteses ao redor.

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

Isso às vezes é chamado de compreensão de gerador, embora eu ache que o nome oficial ainda seja expressão de gerador, não há realmente nenhuma diferença, os parênteses estão lá apenas para tornar a sintaxe válida. Você não precisa deles se estiver transmitindo-os como o único parâmetro para uma função, por exemplo:

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

Basicamente, todas as outras compreensões disponíveis no Python 3 e Python 2.7 são apenas açúcar sintático em torno de uma expressão geradora. Definir compreensões:

>>> {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}

Compreensões de ditado:

>>> 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}

E listar compreensões em 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]

No Python 2, as compreensões de lista não são apenas açúcar sintático. Mas a única diferença é que x no Python 2 vazará para o namespace.

>>> x
9

Enquanto estiver no Python 3, você obterá

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

Isso significa que a melhor maneira de obter uma boa impressão do conteúdo de sua expressão geradora em Python é fazer uma lista de compreensão a partir dela! No entanto, isso obviamente não funcionará se você já tiver um objeto gerador. Isso apenas fará uma lista de um gerador:

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

Nesse caso, você precisará ligar para list():

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

Embora isso funcione, mas é meio estúpido:

>>> [x for x in foo]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Lennart Regebro
fonte
5
O termo oficial continua sendo "expressão geradora" porque a palavra "compreensão" implica em iteração, que é algo que um genexp não faz, como esta pergunta e resposta ilustram bem :)
ncoghlan
2
list( generator-expression )não está imprimindo a expressão do gerador; ele está gerando uma lista (e, em seguida, imprimindo-a em um shell interativo). Em vez de gerar uma lista, no Python 3, você pode dividir a expressão geradora em uma instrução de impressão. Ie) print(*(generator-expression)). Isso imprime os elementos sem vírgulas e sem colchetes no início e no final.
AJNeufeld de
18

Ao contrário de uma lista ou dicionário, um gerador pode ser infinito. Fazer isso não funcionaria:

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

Além disso, a leitura de um gerador o altera, portanto não há uma maneira perfeita de visualizá-lo. Para ver uma amostra da saída do gerador, você poderia fazer

g1 = gen()
[g1.next() for i in range(10)]
Chade
fonte
2
Votado por causa da afirmação de que um gerador pode ser infinito, causando um loop ou uma parada total (dependendo de suas especificações (risos)).
Milan Velebit
Use [next(g1) for i in range(10)]em Python 3.
Deepank
16

Você pode simplesmente envolver a expressão em uma chamada para 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
fonte
15

Ou você pode sempre mapusar um iterador, sem a necessidade de construir uma lista intermediária:

>>> _ = map(sys.stdout.write, (x for x in string.letters if x in (y for y in "BigMan on campus")))
acgimnopsuBM
Ibolla
fonte
3
esta é a única resposta que realmente imprime o conteúdo do gerador sem criar um 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
fonte
Caso o gerador seja infinito, ocorrerá um loop.
Milan Velebit