Разница между пониманием списка и пониманием генератора с `yield` внутри

В чем разница между пониманиями списков и понятием генератора с yield внутри? Оба возвращают объект-генератор (listcomp и genexpr соответственно), но при полной оценке последний добавляет то, что кажется излишним None s.

>>> list([(yield from a) for a in zip("abcde", itertools.cycle("12"))])
['a', '1', 'b', '2', 'c', '1', 'd', '2', 'e', '1']

>>> list(((yield from a) for a in zip("abcde", itertools.cycle("12"))))
['a', '1', None, 'b', '2', None, 'c', '1', None, 'd', '2', None, 'e', '1', None]

Как получилось? Что такое научное объяснение?

Ответ 1

TL;DR: выражение генератора использует неявный yield, который возвращает None из yield from выражения.

На самом деле здесь две вещи ведут себя по-другому. Ваше понимание списка действительно выброшено...

  • Еще раз с ясностью

Понимание этого проще всего, если вы преобразуете выражения в эквивалентные функции. Для ясности, напишите это:

listcomp = [<expr> for a in b]
def listfunc():
    result = []
    for a in b:
        result.append(<expr>)
    return result

gencomp = (<expr> for a in b)
def genfunc():
    for a in b:
        yield <expr>

Чтобы воспроизвести начальные выражения, ключ должен заменить <expr> на (yield from a). Это простая текстовая замена:

def listfunc():
    result = []
    for a in b:
        result.append((yield from a))
    return result

def genfunc():
    for a in b:
        yield (yield from a)

Если b = ((1,), (2,)), мы ожидаем выход 1, 2. Действительно, оба дублируют вывод их соответствующих форм выражения/понимания.

Как объяснялось в другом месте, yield (yield from a) должна сделать вас подозрительной. Однако result.append((yield from a)) должен заставить вас съежиться...

  • Получение ответа

Сначала посмотрим на генератор. Еще одно переписывание делает очевидным, что происходит:

def genfunc():
    for a in b:
        result = (yield from a)
        yield result

Чтобы это было правильно, result должен иметь значение, а именно None. Генератор не yield выражения (yield from a), но его результат. Вы получаете только контент a как побочный эффект оценки выражения.

  • Возвращаясь к вопросу

Если вы проверяете тип своего "понимания списка", он не является list - он является generator. <listcomp> - это просто его имя. Да, это не Луна, это полностью функциональный генератор.

Помните, как наша трансформация давала yield from внутри функции? Yepp, вот как вы определяете генератор! Вот наша функциональная версия, на этот раз с print посыпанным на ней:

def listfunc():
    result = []
    for a in b:
        result.append((yield from a))
        print(result[-1])
    print(result)
    return result

Оценка list(listfunc()) печатает None, None и [None, None] и возвращает [1, 2]. Ваш фактический список содержит тот None, которые прокрались в генератор, как хорошо! Однако он отбрасывается, и результат снова является побочным эффектом. Это то, что на самом деле происходит:

  • Генератор создается при оценке понимания списка /listfunc.
  • Подача его в list итераций по нему...
    • yield from a дает значения a и возвращает None
    • None не сохраняется в списке результатов
  • В конце итерации...

    • return StopIteration со значением [None, None]
    • Конструктор list игнорирует это и выбрасывает значение
  • Мораль этой истории

Не используйте yield from изнутри понимания. Он не делает то, что, по вашему мнению, делает.

Ответ 2

Значение выражения yield from None. Тот факт, что ваш второй пример является выражением генератора, означает, что он уже неявно уступает итератору, поэтому он также даст значение выражения yield from. Подробнее см. .