Когда останавливается выполнение кода в генераторе питона?
Я пытаюсь понять поведение оператора 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 не сможет узнать, что генератор готов, пока вы тоже
- Вернитесь с генератора.
- Поднимите ошибку, и в этом случае все остановится.
В версиях до Python 3.7 генераторы могли повышать StopIteration
указать прекращение. На самом деле, оператор возврата будет эквивалентен raise StopIteration
(если возвращается None
) или же raise StopIteration(return_value)
,
Поэтому, в то время как точный способ, которым вы говорите Python завершить работу генератора, зависит от вас, вы должны быть откровенны в этом. yield
сам по себе не завершает генератор.
TL; DR
Весь код в цикле в генераторе будет всегда выполняться, даже после получения последнего значения, потому что Python может знать только, что это было последнее значение, фактически выполняя весь код.
Ответ заключается в понимании того, что делает цикл в python: он получает итератор (т.е. iter()
) объекта и продолжается до StopIteration
исключение повышено.StopIteration
Исключение выдается, когда код генератора выполнен, что означает получение оператора возврата, который существует функцией (также может быть неявным). Вот почему это не останавливается на yield
спрашивает о следующем yield
пока генератор не сделан.