выход в списке понимания и генераторных выражений



следующее поведение кажется мне довольно нелогичным (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, но третья строка не делает.



кроме того, это кажется странным




  • что понимание списка возвращает генератор, а не список

  • и что выражение генератора, преобразованное в список, и соответствующее понимание списка содержат разные значения.


может ли кто-нибудь предоставить дополнительную информацию?

513   1  

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_VALUE

The 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

    Ничего не найдено.