Есть один тип ответа, который я не чувствую, был дан, среди многих отличных ответов, которые описывают, как использовать генераторы. Вот ответ теории языка программирования:
Оператор yield
в Python возвращает генератор. Генератор в Python - это функция, которая возвращает продолжения (и, в частности, тип сопрограммы, но продолжения представляют более общий механизм для понимания того, что происходит).
Продолжения в теории языков программирования являются гораздо более фундаментальным видом вычислений, но они не часто используются, потому что их чрезвычайно сложно рассуждать, а также очень сложно реализовать. Но идея о том, что такое продолжение, проста: это состояние вычислений, которое еще не закончено. В этом состоянии текущие значения переменных, операции, которые еще предстоит выполнить, и т. Д. Сохраняются. Затем в какой-то момент позже в программе может быть вызвано продолжение, так что переменные программы возвращаются в это состояние и выполняются сохраненные операции.
Продолжения в этом более общем виде могут быть реализованы двумя способами. В способе call/cc
стек программы буквально сохраняется, а затем, когда вызывается продолжение, стек восстанавливается.
В стиле передачи продолжения (CPS), продолжения - это просто обычные функции (только в языках, где функции первого класса), которыми программист явно управляет и передает их подпрограммам. В этом стиле состояние программы представлено замыканиями (и переменными, которые в них кодируются), а не переменными, которые находятся где-то в стеке. Функции, которые управляют потоком управления, принимают продолжение в качестве аргументов (в некоторых вариациях CPS функции могут принимать несколько продолжений) и управляют потоком управления, вызывая их, просто вызывая их и возвращая потом. Очень простой пример стиля передачи продолжения следующий:
def save_file(filename):
def write_file_continuation():
write_stuff_to_file(filename)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
В этом (очень упрощенном) примере программист сохраняет операцию фактической записи файла в продолжение (которая может быть очень сложной операцией с большим количеством деталей для записи), а затем передает это продолжение (т. Е. Как первоклассное замыкание) другому оператору, который выполняет дополнительную обработку, а затем вызывает ее при необходимости (Я часто использую этот шаблон проектирования в реальном программировании GUI, потому что он экономит мне строки кода или, что более важно, управляет потоком управления после запуска событий GUI.)
Остальная часть этого поста, без потери общности, концептуализирует продолжения как CPS, потому что это чертовски много легче понять и прочитать.
Теперь давайте поговорим о генераторах в Python. Генераторы - это определенный подтип продолжения. В то время как продолжения в целом могут сохранять состояние вычислений (т. Е. Стека вызовов программы), генераторы могут сохранять состояние итерации только по итератор . Хотя это определение слегка вводит в заблуждение для определенных случаев использования генераторов. Например:
def f():
while True:
yield 4
Это явно разумная итерация, поведение которой четко определено - каждый раз, когда генератор повторяет ее, он возвращает 4 (и делает это вечно). Но, вероятно, это не прототипный тип итерируемого, который приходит на ум, когда он думает об итераторах (то есть for x in collection: do_something(x)
). Этот пример иллюстрирует мощь генераторов: если что-то является итератором, генератор может сохранить состояние своей итерации.
Повторим: продолжения могут сохранять состояние стека программы, а генераторы могут сохранять состояние итерации. Это означает, что продолжения более мощные, чем генераторы, но также и то, что генераторы намного, намного проще. Их легче реализовать для языкового дизайнера, и программисту проще в их использовании (если у вас есть какое-то время, попробуйте прочитать и понять эту страницу о продолжениях и вызвать / cc ).
Но вы могли бы легко реализовать (и концептуализировать) генераторы как простой, конкретный случай стиля передачи продолжения:
Всякий раз, когда вызывается yield
, он сообщает функции, что нужно продолжить продолжение. Когда функция вызывается снова, она начинается с того места, где она остановилась. Таким образом, в псевдопсевдокоде (то есть не псевдокоде, а не коде) метод генератора next
в основном выглядит следующим образом:
class Generator():
def __init__(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(iterable)
def next(self):
value, next_continuation = self.next_continuation()
self.next_continuation = next_continuation
return value
где ключевое слово yield
на самом деле является синтаксическим сахаром для реальной функции генератора, что-то вроде:
def generatorfun(iterable):
if len(iterable) == 0:
raise StopIteration
else:
return (iterable[0], lambda:generatorfun(iterable[1:]))
Помните, что это просто псевдокод, и фактическая реализация генераторов в Python более сложна. Но в качестве упражнения, чтобы понять, что происходит, попробуйте использовать стиль передачи продолжения для реализации объектов генератора без использования ключевого слова yield
.