Python: распечатать выражение генератора?

103

В оболочке Python, если я ввожу понимание списка, например:

>>> [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']

То же самое для понимания словаря:

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

Если я ввожу выражение генератора, я получаю не такой дружелюбный ответ:

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

Я знаю, что могу это сделать:

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

Помимо этого (или написания вспомогательной функции) могу ли я легко оценить и распечатать этот объект-генератор в интерактивной оболочке?

волк
источник
2
В чем здесь настоящая проблема? Что вам не хватает?
Андреас Юнг
3
@pynator: «Настоящая проблема» заключается в том, что я хочу иметь возможность распечатать содержимое, generator objectпоскольку я интерактивно выстраиваю понимание в интерактивной подсказке. Звонок list(_)делает это. То, что я сделал, - это использование списков, а затем преобразование их в genexp в более крупном коде. Они могут дать сбой во время выполнения, чего не могут сделать перечисления.
волк
5
Короткий ответ: выражение генератора не может быть напечатано, потому что его значения не существуют; они создаются по запросу. Что вы можете сделать (при условии, что генератор когда-нибудь остановится), так это получить из него все значения, например, с list(), а затем распечатать их.
Кос,

Ответы:

161

Быстрый ответ:

Обращение list()с выражением генератора (почти) в точности эквивалентно заключению его в []скобки. Так что да, ты можешь сделать

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

Но вы также можете сделать

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

Да, это превратит выражение генератора в понимание списка. То же самое и со списком вызовов (). Таким образом, чтобы преобразовать выражение генератора в список, нужно заключить его в скобки.

Детальное объяснение:

Выражение генератора - это «голое» forвыражение. Вот так:

x*x for x in range(10)

Теперь вы не можете вставить это в отдельную строку, вы получите синтаксическую ошибку. Но вы можете заключить его в скобки.

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

Иногда это называют пониманием генератора, хотя я думаю, что официальное название по-прежнему является выражением генератора, на самом деле нет никакой разницы, скобки нужны только для того, чтобы синтаксис был действительным. Они вам не нужны, если вы передаете его как единственный параметр функции, например:

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

По сути, все другие понимания, доступные в Python 3 и Python 2.7, представляют собой просто синтаксический сахар вокруг выражения генератора. Установить понимание:

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

И перечислите понимание в 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]

В Python 2 понимание списков - это не просто синтаксический сахар. Но единственная разница в том, что x будет в Python 2 просачиваться в пространство имен.

>>> x
9

В Python 3 вы получите

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

Это означает, что лучший способ получить красивую распечатку содержимого выражения генератора на Python - это составить из него список! Однако это явно не сработает, если у вас уже есть объект-генератор. В результате будет создан список из одного генератора:

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

В этом случае вам нужно будет позвонить list():

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

Хотя это работает, но глупо:

>>> [x for x in foo]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Леннарт Регебро
источник
5
Официальным термином остается «выражение-генератор», потому что слово «понимание» подразумевает итерацию, чего не делает genexp , как хорошо иллюстрируют этот вопрос и ответ :)
ncoghlan 02
2
list( generator-expression )не печатает выражение генератора; он создает список (а затем распечатывает его в интерактивной оболочке). Вместо создания списка в Python 3 вы можете преобразовать выражение генератора в оператор печати. Т.е.) print(*(generator-expression)). Это печатает элементы без запятых и без скобок в начале и в конце.
AJNeufeld
18

В отличие от списка или словаря, генератор может быть бесконечным. Это не сработает:

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

Кроме того, чтение генератора меняет его, поэтому нет идеального способа его просмотреть. Чтобы увидеть образец вывода генератора, вы можете сделать

g1 = gen()
[g1.next() for i in range(10)]
Чад
источник
2
Проголосовали из-за утверждения, что генератор может быть бесконечным, что вызывает цикл или полную остановку (в зависимости от ваших требований (смеется)).
Милан Велебит,
Использование [next(g1) for i in range(10)]в Python 3.
Deepank
16

Вы можете просто заключить выражение в вызов 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

Или вы всегда можете mapиспользовать итератор без необходимости создавать промежуточный список:

>>> _ = map(sys.stdout.write, (x for x in string.letters if x in (y for y in "BigMan on campus")))
acgimnopsuBM
лболла
источник
3
это единственный ответ, который фактически печатает содержимое генератора без создания огромного объекта.
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']
Андреас Юнг
источник
Если генератор бесконечен, это вызовет цикл.
Милан Велебит,