Когда останавливается выполнение кода в генераторе питона?

Я пытаюсь понять поведение оператора yield, создавая генератор, который ведет себя аналогично встроенной функции enumerate, но я наблюдаю несоответствия в зависимости от того, как я выполняю итерацию.

def enumerate(sequence, start=0):
n = start
for elem in sequence:
    print("Before the 'yield' statement in the generator, n = {}".format(n))
    yield n, elem
    n += 1
    print("After the 'yield' statement in the generator, n = {}".format(n))

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

a = 'foo'
b = enumerate(a)
n1,v1 = next(b)
print('n1 = {}, v1 = {}\n'.format(n1,v1))
n2,v2 = next(b)
print('n2 = {}, v2 = {}'.format(n2,v2))

В этом случае генератор останавливается точно в операторе yield и возобновляется в n+=1 со вторым оператором next:

Before the 'yield' statement in the generator, n = 0
n1 = 0, v1 = f

After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n2 = 1, v2 = o

Однако, если я использую цикл for ниже, генератор, похоже, не останавливается на операторе yield.

for n,v in enumerate(a[0:1]):
    print('n = {}, v = {}'.format(n,v))

Вот что я получаю:

Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1

Редактировать с учетом комментариев

Я понимаю, что перебираю только один элемент, но я не ожидал увидеть самое последнее предложение "После оператора yield" в генераторе "(которое появляется, даже если я перебираю ВСЕ элементы.

print('\n\n')
for n,v in enumerate(a):
    print('n = {}, v = {}'.format(n,v))

Before the 'yield' statement in the generator, n = 0
n = 0, v = f
After the 'yield' statement in the generator, n = 1
Before the 'yield' statement in the generator, n = 1
n = 1, v = o
After the 'yield' statement in the generator, n = 2
Before the 'yield' statement in the generator, n = 2
n = 2, v = o
After the 'yield' statement in the generator, n = 3

Почему это происходит?

2 ответа

Решение

Фундаментальная проблема здесь заключается в том, что вы путаете тот факт, что вы знаете, когда генератор будет исчерпан, просто взглянув на него, с тем фактом, что Python может узнать только при запуске кода. Когда Python достигает yield что вы считаете последним, он на самом деле не знает, что это последний. Что если ваш генератор выглядит так:

def enumeratex(x, start=0):
    for elem in x:
        yield start, x
        start += 1
    yield start, None

Здесь, по причинам, которые никто никогда не узнает, финал None элемент возвращается после основного цикла генератора. Python не сможет узнать, что генератор готов, пока вы тоже

  1. Вернитесь с генератора.
  2. Поднимите ошибку, и в этом случае все остановится.

В версиях до Python 3.7 генераторы могли повышать StopIteration указать прекращение. На самом деле, оператор возврата будет эквивалентен raise StopIteration (если возвращается None) или же raise StopIteration(return_value),

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

TL; DR

Весь код в цикле в генераторе будет всегда выполняться, даже после получения последнего значения, потому что Python может знать только, что это было последнее значение, фактически выполняя весь код.

Ответ заключается в понимании того, что делает цикл в python: он получает итератор (т.е. iter()) объекта и продолжается до StopIteration исключение повышено.StopIteration Исключение выдается, когда код генератора выполнен, что означает получение оператора возврата, который существует функцией (также может быть неявным). Вот почему это не останавливается на yieldспрашивает о следующем yield пока генератор не сделан.

Другие вопросы по тегам