Подтвердить что ты не робот

(list | set | dict), содержащее выражение yield, не возвращает (list | set | dict)

Python 3.3

Я построил эту слегка загадочную часть python 3.3:

>>> [(yield from (i, i + 1, i)) for i in range(5)]
<generator object <listcomp> at 0x0000008666D96900>
>>> list(_)
[0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4]

Если я использую понимание генератора внутри конструктора списка, я получаю другой результат:

>>> list((yield from (i, i + 1, i)) for i in range(5))
[0, 1, 0, None, 1, 2, 1, None, 2, 3, 2, None, 3, 4, 3, None, 4, 5, 4, None]

Почему нет списка, возвращающего список?

Python 2.7

Я могу получить аналогичный нечетный эффект в python 2 (используя понимание набора, потому что в представлениях списков есть странная область):

>>> {(yield i) for i in range(5)}
<generator object <setcomp> at 0x0000000004A06120>
>>> list(_)
[0, 1, 2, 3, 4, {None}]

И при использовании понимания генератора:

>>> list((yield i) for i in range(5))
[0, None, 1, None, 2, None, 3, None, 4, None]

Откуда появился {None}?

4b9b3361

Ответ 1

List (set, dict) позволяет перевести на другую структуру кода из выражений генератора. Давайте посмотрим на установившееся понимание:

def f():
    return {i for i in range(10)}

dis.dis(f.__code__.co_consts[1])
  2           0 BUILD_SET                0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                12 (to 21)
              9 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             15 SET_ADD                  2
             18 JUMP_ABSOLUTE            6
        >>   21 RETURN_VALUE        

Сравните с эквивалентным выражением генератора:

def g():
    return (i for i in range(10))

dis.dis(g.__code__.co_consts[1])
  2           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        

Вы заметите, что если выражение генератора имеет yield, то задание набора хранит значение непосредственно в наборе, который он создает.

Это означает, что если вы добавите выражение yield в тело выражения генератора, оно обрабатывается неотличимо от yield, которое язык создает для тела генератора; в результате вы получаете два (или более) значения за итерацию.

Однако, если вы добавите yield в список (set, dict), то понимание преобразуется из функции, создающей список (set, dict) в генератор, который выполняет инструкции yield, а затем возвращает построенный список (set, dict). {None} в результате определения набора устанавливается набор, построенный из каждого из None, который выражения yield оценивают.


Наконец, почему Python 3.3 не создает {None}? (Обратите внимание, что предыдущие версии Python 3 делают.) Это из-за PEP 380 (поддержка a.k.a. yield from). До Python 3.3, a return в генераторе есть SyntaxError: 'return' with argument inside generator; поэтому наши yield ing-понимания используют поведение undefined, но фактический результат кода операции RETURN_VALUE заключается в том, чтобы просто генерировать другое (окончательное) значение из генератора. В Python 3.3 поддерживается явная поддержка return value; a RETURN_VALUE код операции приводит к повышению StopIteration, что приводит к остановке генератора без создания конечного значения.

Ответ 2

Используя this в качестве ссылки:

Описание Python 3

Это:

values = [(yield from (i, i + 1, i)) for i in range(5)]

Переводит в Python 3.x:

def _tmpfunc(): 
    _tmp = [] 
    for x in range(5): 
        _tmp.append(yield from (i, i + 1, i)) 
    return _tmp 
values = _tmpfunc()

В результате получается values, содержащий генератор

Затем этот генератор будет выводиться из каждого (i, i + 1, i), пока, наконец, не достигнет оператора return. В python 3 это будет throw StopIteration(_tmp) - однако это исключение игнорируется конструктором list.


С другой стороны, это:

list((yield from (i, i + 1, i)) for i in range(5))

Переводит в Python 3.x:

def _tmpfunc():
    for x in range(5): 
        yield (yield from (i, i + 1, i))

values = list(_tmpfunc())

На этот раз каждый раз, когда завершается yield from, он вычисляет None, который затем yield ed среди других значений.