выход в списке понимания и генераторных выражений
следующее поведение кажется мне довольно нелогичным (Python 3.4):
>>> [(yield i) for i in range(3)]
<generator object <listcomp> at 0x0245C148>
>>> list([(yield i) for i in range(3)])
[0, 1, 2]
>>> list((yield i) for i in range(3))
[0, None, 1, None, 2, None]
промежуточные значения последней строки на самом деле не всегда None, Они все, что мы send в генератор, эквивалентный (я думаю) в следующий генератор:
def f():
for i in range(3):
yield (yield i)
мне кажется забавным, что эти три строки вообще работают. Элемент ссылка говорит, что yield допускается только в определении функции (хотя я может быть неправильно понял и / или он может быть просто скопирован из более старой версии). Первые две строки производят SyntaxError в Python 2.7, но третья строка не делает.
кроме того, это кажется странным
- что понимание списка возвращает генератор, а не список
- и что выражение генератора, преобразованное в список, и соответствующее понимание списка содержат разные значения.
может ли кто-нибудь предоставить дополнительную информацию?
1 ответ:
Примечание: это была ошибка в обработке CPython
yieldв понимании и генераторных выражениях, исправленных в Python 3.8, с предупреждением об устаревании в Python 3.7. Смотрите отчет об ошибке Python и что нового записи Python 3.7 и Python 3.8.генераторные выражения, а также набор и dict понимания компилируются в (генератор) функциональные объекты. В Python 3, list comprehensions получают одинаковую обработку; все они, по сути, являются новой вложенной областью.
вы можете увидеть это, если попытаетесь разобрать выражение генератора:
>>> 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 3.7 позволяет это (именно поэтому код компилируется), ноyieldспецификация выражение показывает, что с помощьюyieldздесь на самом деле не должно работать:выражение выхода используется только при определении генератор функция и, таким образом, может использоваться только в теле определения функции.
это было подтверждено как ошибка в вопрос 10544. Разрешение ошибки заключается в том, что с помощью
yieldиyield fromбудет поднятьSyntaxErrorв Python 3.8; в Python 3.7 вызываетDeprecationWarningдля обеспечения остановки код с использованием этой конструкции. Вы увидите то же самое предупреждение в Python 2.7.15 и если вы используете-3переключатель командной строки включение предупреждений о совместимости Python 3.предупреждение 3.7.0b1 выглядит так; превращение предупреждений в ошибки дает вам
SyntaxErrorисключение, как вы бы в 3.8:>>> [(yield i) for i in range(3)] <stdin>:1: DeprecationWarning: 'yield' inside list comprehension <generator object <listcomp> at 0x1092ec7c8> >>> import warnings >>> warnings.simplefilter('error') >>> [(yield i) for i in range(3)] File "<stdin>", line 1 SyntaxError: 'yield' inside list comprehensionразличия между тем, как
yieldв списке понимание иyieldв генераторе выражение operate вытекает из различий в том, как эти два выражения реализуются. В 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_VALUEThe
YIELD_VALUEкод операции на байт-код индексы 15 и 12 соответственно является дополнительным, кукушка в гнезде. Таким образом, для list-comprehension-turned-generator у вас есть 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возвращаемые значения по-прежнему добавляются в предполагаемый словарь или объект set, а возвращаемое значение "дается" последним, а не присоединяется к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])]
Comments