Ответ 1
Выражения генератора, а также выражения set и dict компилируются в объекты функции (генератора). В Python 3 переписные справки получают одинаковое обращение; все они, по существу, представляют собой новую вложенную область.
Это можно увидеть, если попытаться разобрать выражение генератора:
>>> dis.dis(compile("(i for i in range(3))", '', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (range)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
Вышеприведенное показывает, что выражение генератора компилируется в объект кода, загружаемый как функция (MAKE_FUNCTION
создает объект функции из объекта кода). Ссылка .co_consts[0]
позволяет нам видеть объект кода, сгенерированный для выражения, и использует YIELD_VALUE
так же, как функция генератора.
Таким образом, выражение yield
работает в этом контексте, поскольку компилятор видит их как скрытые функции.
Тем не менее, я рассматриваю это как ошибку; yield
не имеет места в этих выражениях. Это позволяет грамматика Python (поэтому компилятор кода), но спецификация выражения yield
показывает, что использование yield
здесь не должно работать:
Выражение yield используется только при определении функции генератора и, следовательно, может использоваться только в теле определения функции.
Это уже привело к запутывающим ошибкам, где кто-то пытался использовать yield
в функции генератора внутри выражения генератора, ожидая применения yield
к функции. Разработчики Python знают об этой проблеме, поскольку запись Guido о записи не предназначена:
Я думаю, что это определенно неправильно, как это работает в 3.x. (Тем более, что он работает как ожидается в 2.x.)
Я согласен с предпочтениями Inyeol исправлений: (1) заставить его работать правильно для listcomps, а также genexps, (2) если это невозможно, запретите выход в genexp или listcomp.
Различия между тем, как yield
в понимании списка и yield
в выражении генератора работают, связаны с различиями в том, как эти два выражения реализованы. В Python 3 в понимании списка используются вызовы LIST_APPEND
для добавления вершины стека к строящему списку, тогда как выражение генератора вместо этого дает это значение. Добавление в (yield <expr>)
просто добавляет еще один код операции YIELD_VALUE
для:
>>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 13 (to 22)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 YIELD_VALUE
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 6
>> 22 RETURN_VALUE
>>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 YIELD_VALUE
14 POP_TOP
15 JUMP_ABSOLUTE 3
>> 18 LOAD_CONST 0 (None)
21 RETURN_VALUE
Код операции YIELD_VALUE
в индексах 15 и 12 байт-кода соответственно является дополнительным, кукушкой в гнезде. Итак, для генератора, использующего список, вы получаете 1 выход, каждый раз создавая верхнюю часть стека (заменяя верхнюю часть стека на возвращаемое значение yield
), а для варианта выражения генератора вы получаете верхнюю часть стек (целое число), а затем снова выполнить, но теперь стек содержит возвращаемое значение yield
, и вы получите второй раз None
.
Для понимания списка, то объект-вывод list
все еще возвращается, но Python 3 видит это как генератор, поэтому возвращаемое значение вместо этого привязывается к StopIteration
исключение в качестве атрибута value
:
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3)) # avoid exhausting the generator
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
Те None
объекты - это возвращаемые значения из выражений yield
.
И повторить это снова; эта же проблема относится к словарю и устанавливает понимание в Python 2 и Python 3; в Python 2 значения возвращаемого значения yield
по-прежнему добавляются к предполагаемому словарю или заданному объекту, а возвращаемое значение "возвращает" последним вместо привязки к исключению StopIteration
:
>>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()})
['bar', 'foo', 'eggs', 'spam', {None: None}]
>>> list({(yield i) for i in range(3)})
[0, 1, 2, set([None])]