Python: stampare un'espressione del generatore?


103

Nella shell Python, se inserisco una lista di comprensione come:

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

Ottengo un risultato ben stampato:

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

Lo stesso per una comprensione del dizionario:

>>> {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 inserisco un'espressione del generatore, non ottengo una risposta così amichevole:

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

So che posso fare questo:

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

Oltre a questo (o scrivendo una funzione di supporto) posso facilmente valutare e stampare quell'oggetto generatore nella shell interattiva?


2
Qual è il vero problema qui? Cosa ti stai perdendo?
Andreas Jung

3
@pynator: Il "vero problema" è solo che voglio essere in grado di stampare il contenuto generator objectmentre costruisco interattivamente una comprensione al prompt interattivo. Chiamare lo list(_)fa. Quello che ho fatto è stato utilizzare le comprensioni delle liste e poi trasformarle in genexp in un codice più grande. Questi possono fallire in fase di esecuzione in modi che non fanno le comprensioni degli elenchi.
il lupo

5
La risposta breve è che un'espressione del generatore non può essere stampata perché i suoi valori non esistono; vengono generati su richiesta. Quello che puoi fare (supponendo che il generatore si fermi prima o poi) è ottenere tutti i valori da esso, come con list(), e poi stamparli.
Kos

Risposte:


161

Risposta rapida:

Fare list()intorno a un'espressione del generatore è (quasi) esattamente equivalente ad avere []parentesi attorno ad essa. Quindi sì, puoi farlo

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

Ma puoi farlo altrettanto bene

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

Sì, questo trasformerà l'espressione del generatore in una comprensione dell'elenco. È la stessa cosa e chiama list () su di esso. Quindi il modo per trasformare un'espressione del generatore in un elenco è racchiudere tra parentesi.

Spiegazione dettagliata:

Un'espressione generatrice è un'espressione "nuda" for. Così:

x*x for x in range(10)

Ora, non puoi incollarlo su una riga da solo, otterrai un errore di sintassi. Ma puoi racchiuderlo tra parentesi.

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

Questa a volte è chiamata comprensione del generatore, anche se penso che il nome ufficiale sia ancora espressione del generatore, non c'è davvero alcuna differenza, le parentesi sono lì solo per rendere valida la sintassi. Non ti servono se lo stai passando come unico parametro a una funzione, ad esempio:

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

Fondamentalmente tutte le altre comprensioni disponibili in Python 3 e Python 2.7 sono solo zucchero sintattico attorno a un'espressione del generatore. Imposta le comprensioni:

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

Comprensioni dei dettami:

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

Ed elenca le comprensioni in 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]

In Python 2, le liste di comprensione non sono solo zucchero sintattico. Ma l'unica differenza è che x sotto Python 2 trapelerà nello spazio dei nomi.

>>> x
9

Mentre sotto Python 3 otterrai

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

Ciò significa che il modo migliore per ottenere una bella stampa del contenuto dell'espressione del tuo generatore in Python è di ricavarne una comprensione dell'elenco! Tuttavia, questo ovviamente non funzionerà se hai già un oggetto generatore. In questo modo verrà creato un elenco di un generatore:

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

In tal caso dovrai chiamare list():

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

Anche se funziona, ma è un po 'stupido:

>>> [x for x in foo]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

5
Il termine ufficiale rimane "espressione generatrice" perché la parola "comprensione" implica l'iterazione, che è una cosa che un genexp non fa, come questa domanda e risposta illustrano bene :)
ncoghlan

2
list( generator-expression )non stampa l'espressione del generatore; sta generando un elenco (e quindi stampandolo in una shell interattiva). Invece di generare un elenco, in Python 3, potresti suddividere l'espressione del generatore in un'istruzione print. Vale a dire) print(*(generator-expression)). Questo stampa gli elementi senza virgole e senza parentesi all'inizio e alla fine.
AJNeufeld

18

A differenza di un elenco o di un dizionario, un generatore può essere infinito. Fare questo non funzionerebbe:

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

Inoltre, leggere un generatore lo cambia, quindi non c'è un modo perfetto per visualizzarlo. Per vedere un campione dell'output del generatore, potresti fare

g1 = gen()
[g1.next() for i in range(10)]

2
Votato per l'affermazione che un generatore può essere infinito, causando quindi un loop o un arresto totale (a seconda delle specifiche (lol)).
Milan Velebit

Usa [next(g1) for i in range(10)]in Python 3.
Deepank

16

Puoi semplicemente racchiudere l'espressione in una chiamata 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']

15

Oppure puoi sempre mapsu un iteratore, senza la necessità di costruire un elenco intermedio:

>>> _ = map(sys.stdout.write, (x for x in string.letters if x in (y for y in "BigMan on campus")))
acgimnopsuBM

3
questa è l'unica risposta che effettivamente stampa i contenuti del generatore senza creare oggetti enormi.
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']

Nel caso in cui il generatore sia infinito, causerà un loop.
Milan Velebit
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.