Python: imprimer une expression de générateur?

103

Dans le shell Python, si j'entre une compréhension de liste telle que:

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

J'obtiens un résultat bien imprimé:

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

Idem pour une compréhension de dictionnaire:

>>> {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 j'entre une expression de générateur, je n'obtiens pas une réponse aussi amicale:

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

Je sais que je peux le faire:

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

En dehors de cela (ou en écrivant une fonction d'assistance), puis-je facilement évaluer et imprimer cet objet générateur dans le shell interactif?

le loup
la source
2
Quel est le vrai problème ici? Qu'est-ce que vous manque?
Andreas Jung
3
@pynator: Le "vrai problème" est simplement que je veux pouvoir imprimer le contenu de generator objectcar je construis interactivement une compréhension à l'invite interactive. L'appel list(_)fait ça. Ce que j'ai fait, c'est utiliser des compréhensions de liste, puis les transformer en genexp dans un code plus grand. Celles-ci peuvent échouer au moment de l'exécution d'une manière que les compréhensions de liste ne font pas.
le loup
5
La réponse courte est qu'une expression de générateur ne peut pas être imprimée car ses valeurs n'existent pas; ils sont générés à la demande. Ce que vous pouvez faire (en supposant que le générateur s'arrête parfois), c'est en extraire toutes les valeurs, comme avec list(), puis les imprimer.
Kos

Réponses:

161

Réponse rapide:

Faire le list()tour d'une expression de générateur équivaut (presque) exactement à avoir des []crochets autour d'elle. Alors oui, tu peux faire

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

Mais tu peux tout aussi bien faire

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

Oui, cela transformera l'expression du générateur en une compréhension de liste. C'est la même chose et la liste d'appels () dessus. Ainsi, la manière de transformer une expression de générateur en liste est de la mettre entre crochets.

Explication détaillée:

Une expression génératrice est une expression «nue» for. Ainsi:

x*x for x in range(10)

Maintenant, vous ne pouvez pas coller cela sur une ligne seule, vous obtiendrez une erreur de syntaxe. Mais vous pouvez mettre des parenthèses autour.

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

Cela s'appelle parfois une compréhension du générateur, même si je pense que le nom officiel est toujours l'expression du générateur, il n'y a pas vraiment de différence, les parenthèses ne sont là que pour rendre la syntaxe valide. Vous n'en avez pas besoin si vous le transmettez comme seul paramètre à une fonction, par exemple:

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

Fondamentalement, toutes les autres compréhensions disponibles dans Python 3 et Python 2.7 ne sont que du sucre syntaxique autour d'une expression de générateur. Définir les compréhensions:

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

Compréhensions de dictées:

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

Et lister les compréhensions sous 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]

Sous Python 2, la compréhension de liste n'est pas seulement du sucre syntaxique. Mais la seule différence est que x va sous Python 2 s'infiltrer dans l'espace de noms.

>>> x
9

Sous Python 3, vous obtiendrez

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

Cela signifie que la meilleure façon d'obtenir une belle impression du contenu de votre expression de générateur en Python est d'en faire une compréhension de liste! Cependant, cela ne fonctionnera évidemment pas si vous avez déjà un objet générateur. Faire cela fera juste une liste d'un générateur:

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

Dans ce cas, vous devrez appeler list():

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

Bien que cela fonctionne, mais c'est un peu stupide:

>>> [x for x in foo]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Lennart Regebro
la source
5
Le terme officiel reste «expression génératrice» car le mot «compréhension» implique une itération, ce qui est une chose qu'un genexp ne fait pas , comme l'illustrent joliment cette question et cette réponse :)
ncoghlan
2
list( generator-expression )n'imprime pas l'expression du générateur; il génère une liste (puis l'imprime dans un shell interactif). Au lieu de générer une liste, dans Python 3, vous pouvez splater l'expression du générateur dans une instruction d'impression. Ie) print(*(generator-expression)). Cela imprime les éléments sans virgules et sans crochets au début et à la fin.
AJNeufeld
18

Contrairement à une liste ou à un dictionnaire, un générateur peut être infini. Cela ne fonctionnerait pas:

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

De plus, la lecture d'un générateur le change, il n'y a donc pas de moyen parfait de le visualiser. Pour voir un échantillon de la sortie du générateur, vous pouvez faire

g1 = gen()
[g1.next() for i in range(10)]
tchad
la source
2
Voté en raison de l'affirmation selon laquelle un générateur peut être infini, provoquant donc une boucle ou un arrêt total (selon vos spécifications (lol)).
Milan Velebit
Utilisation [next(g1) for i in range(10)]en Python 3.
Deepank
16

Vous pouvez simplement envelopper l'expression dans un appel à 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
la source
15

Ou vous pouvez toujours mapsur un itérateur, sans avoir besoin de créer une liste intermédiaire:

>>> _ = map(sys.stdout.write, (x for x in string.letters if x in (y for y in "BigMan on campus")))
acgimnopsuBM
lbolla
la source
3
c'est la seule réponse qui imprime réellement le contenu du générateur sans créer d'objet énorme.
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
la source
Si le générateur est infini, cela provoquera une boucle.
Milan Velebit