Генераторы
Мы уже встречались с ситуациями, где работаем с последовательностью элементов и нам нужен следующий элемент (или элементы) этой последовательности, но при этом не хочется каждый раз формировать всю последовательность до нужной точки заново. Некоторые рекурсивные последовательности, например числа Фибоначчи, являются хорошим примером такой ситуации. Если каждый вызов функции рекурсивно генерирует всю последовательность до нужного места, мы снова и снова создаем начало этой последовательности.
Генераторы Python — это способ производить только следующий элемент последовательности тогда, когда он нужен, фактически выполняя процесс генерации последовательности только один раз (для данного запуска программы). Они в основном работают как обычные функции: их можно вызывать, и они возвращают значе ния. Но значение, которое возвращает функция-генератор, отличается от значения обычной функции. Обычная функция при одних и тех же аргументах должна каждый раз возвращать одно и то же значение. Функция-генератор, напротив, должна помнить свое текущее состояние и возвращать следующий элемент последовательности, который может отличаться от предыдущего.
Как и для большинства задач программирования, существует много способов добиться поведения, похожего на генераторы. Но генераторы могут сделать программу понятнее, а в некоторых ситуациях сэкономить память или другие вычислительные ресурсы.
Ключевое слово yield
Функция-генератор должна содержать ключевое слово yield, которое отмечает значение, возвращаемое функцией. Рассмотрим функцию, которая генерирует целые числа, начиная с нуля и заканчивая заранее заданным максимальным значением:
def counter(max_value: int):
number = 0
while number <= max_value:
yield number
number += 1
Теперь функцию counter можно передать аргументом функции next():
if __name__ == "__main__":
numbers = counter(10)
print("First value:")
print(next(numbers))
print("Second value:")
print(next(numbers))First value: 0 Second value: 1
Как видно из примера выше, ключевое слово yield похоже на ключевое слово return: оба используются для определения возвращаемого значения. Разница в том, что yield не «закрывает» функцию в том же смысле, что return. Функция-генератор с ключевым словом yield отслеживает свое состояние, и при следующем вызове продолжит работу с того же состояния.
Этому генератору также требуется максимальное значение, которым в примере выше было 10. Когда у генератора заканчиваются значения, он возбуждает исключение StopIteration:
if __name__ == "__main__":
# creates a generator with maximum value 1
numbers = counter(1)
print(next(numbers))
print(next(numbers))
print(next(numbers))Исключение можно перехватить блоком try - except:
if __name__ == "__main__":
numbers = counter(1)
try:
print(next(numbers))
print(next(numbers))
print(next(numbers))
except StopIteration:
print("ran out of numbers")0 1 ran out of numbers
Пройти по всем элементам генератора легко с помощью цикла for:
if __name__ == "__main__":
numbers = counter(5)
for number in numbers:
print(number)0 1 2 3 4 5
У генераторов не обязательно должно быть определенное максимальное значение или точка завершения. Они могут генерировать значения бесконечно (естественно, в пределах других вычислительных и физических ограничений).
Однако обратите внимание: проход по генератору с помощью цикла for работает только если генератор в какой-то момент завершается. Если генератор построен на бесконечном цикле, попытка пройти по нему простым циклом for приведет к бесконечному выполнению, как и цикл while без условия окончания или break.
Генераторные выражения
Чтобы создать генератор, не обязательно определять функцию. Вместо этого можно использовать конструкцию, похожую на генератор списка. На этот раз для обозначения генератора используются круглые скобки, а не список или словарь:
# This generator returns squares of integers
squares = (x ** 2 for x in range(1, 64))
print(squares) # the printout of a generator object isn't too informative
for i in range(5):
print(next(squares))<generator object <genexpr> at 0x000002B4224EBFC0> 1 4 9 16 25
В следующем примере мы выводим подстроки английского алфавита, каждая длиной в три символа. Здесь выводятся первые 10 элементов генератора:
substrings = ("abcdefghijklmnopqrstuvwxyz"[i : i + 3] for i in range(24))
# print out first 10 substrings
for i in range(10):
print(next(substrings))abc bcd cde def efg fgh ghi hij ijk jkl
Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.