#deeppython #otus

Все мы знаем и любим генераторы в Python. По сути, генератор - это итератор, который можно использовать в цикле, как обычно, но генератор дополнительно содержит внутри ключевое слово yield. После каждого yield генератор временно прекращает исполнение и возвращает управление, при следующем вызове стартуя с того места, где закончил в прошлый раз, при этом сохраняя состояние и значения переменных между вызовами. Но, черт побери, как он это делает?

Объект генератор, помимо всего прочего, содержит в себе указатель на текущий execution frame, который в свою очередь содержит стек вызова для данного генератора. Во время вызова next(gen_object) вызывается PyEval_EvalFrame для текущего execution frame’а. Это одна из самых главных функций интерпретатора, внутри, она, в том числе, знает про ключевое слово yield

TARGET(YIELD_VALUE) {

retval = POP();

f->f_stacktop = stack_pointer;

why = WHY_YIELD;

goto fast_yield;

}

В данном случае возвращается значение, а текущий фрейм сохраняется (f->f_stacktop = stack_pointer), так что после следующего next’а можно продолжить там, где остановились, ведь PyEval_EvalFrame будет вызван на том же фрейме, что и раньше, с тем же стеком и состоянием. В обычных функциях f_stacktop приравнивается к NULL.