4.1 Знакомство с генераторами
Генераторы — это функции, которые возвращают объект-итератор. Эти итераторы генерируют значения по мере их запроса, что позволяет обрабатывать потенциально большие наборы данных, не загружая их полностью в память.
Есть несколько способов создавать генераторы, ниже будут рассмотрены самые популярные из них.
Генераторы на основе функций
Генераторы создаются с помощью ключевого слова yield внутри функции. Когда функция с yield вызывается, она возвращает объект-генератор, но не выполняет код внутри функции сразу. Вместо этого выполнение приостанавливается на выражении yield и возобновляется при каждом вызове метода __next__() объекта-генератора.
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = count_up_to(5)
print(next(counter)) # Вывод: 1
print(next(counter)) # Вывод: 2
print(next(counter)) # Вывод: 3
print(next(counter)) # Вывод: 4
print(next(counter)) # Вывод: 5
Если в функции есть оператор yield, то Python вместо традиционного исполнения функции создает объект-генератор, который управляет состоянием выполнения функции.
Генераторные выражения
Генераторные выражения похожи на списковые выражения (List Comprehension), но создаются с использованием круглых скобок вместо квадратных. Они также возвращают объект-генератор.
squares = (x ** 2 for x in range(10))
print(next(squares)) # Вывод: 0
print(next(squares)) # Вывод: 1
print(next(squares)) # Вывод: 4
Какой из способов вам больше нравится?
3.2 Преимущества генераторов
Эффективное использование памяти
Генераторы вычисляют значения на лету, что позволяет обрабатывать большие данные, не загружая их полностью в память. Это делает генераторы идеальным выбором для работы с большими наборами данных или потоками данных.
def large_range(n):
for i in range(n):
yield i
for value in large_range(1000000):
# Обрабатываем значения по одному
print(value)
Ленивые вычисления
Генераторы выполняют ленивые вычисления, что означает, что они вычисляют значения только тогда, когда это необходимо. Это позволяет избежать ненужных вычислений и улучшает производительность.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
Удобство синтаксиса
Генераторы предоставляют удобный синтаксис для создания итераторов, что упрощает написание и чтение кода.
3.3 Использование генераторов
Примеры использования генераторов в стандартной библиотеке
Многие функции в стандартной библиотеке Python используют генераторы. Например, функция range() возвращает объект-генератор, который генерирует последовательность чисел.
for i in range(10):
print(i)
Да, мир уже никогда не будет прежним.
Создание бесконечных последовательностей
Генераторы позволяют создавать бесконечные последовательности, которые могут быть полезны в различных сценариях, таких как генерация бесконечных потоков данных.
def natural_numbers():
n = 1
while True:
yield n
n += 1
naturals = natural_numbers()
for _ in range(10):
print(next(naturals))
Использование send() и close()
Объекты-генераторы поддерживают методы send() и close(), которые позволяют отправлять значения обратно в генератор и завершать его выполнение.
def echo():
while True:
received = yield
print(received)
e = echo()
next(e) # Запускаем генератор
e.send("Hello, world!") # Вывод: Hello, world!
e.close()
3.4 Генераторы на практике
Генераторы и исключения
Генераторы могут обрабатывать исключения, что делает их мощным инструментом для написания более устойчивого кода.
def controlled_execution():
try:
yield "Start"
yield "Working"
except GeneratorExit:
print("Generator closed")
gen = controlled_execution()
print(next(gen)) # Вывод: Start
print(next(gen)) # Вывод: Working
gen.close() # Вывод: Generator closed
Работу с исключениями мы рассмотрим в следующих лекциях, но думаю вам будет полезно узнать, что генераторы отлично с ними работают.
Вложенные генераторы
Генераторы могут быть вложенными, что позволяет создавать сложные итерационные структуры.
def generator1():
yield from range(3)
yield from "ABC"
for value in generator1():
print(value)
# Вывод
0
1
2
A
B
C
Объяснение:
yield from: Эта конструкция используется для делегирования части операций другому генератору, что позволяет упрощать код и улучшать читаемость.
Генераторы и производительность
Использование генераторов может значительно улучшить производительность программ за счет уменьшения использования памяти и более эффективного выполнения итераций.
Пример сравнения списков и генераторов
import time
import sys
def memory_usage(obj):
return sys.getsizeof(obj)
n = 10_000_000
# Использование списка
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)
# Использование генератора
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp) # Вычисляем сумму для сравнимости результатов
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)
print(f"Список:")
print(f" Время: {list_time:.2f} сек")
print(f" Память: {list_memory:,} байт")
print(f"\nГенератор:")
print(f" Время: {gen_time:.2f} сек")
print(f" Память: {gen_memory:,} байт")
Список:
Время: 0.62 сек
Память: 89,095,160 байт
Генератор:
Время: 1.13 сек
Память: 200 байт
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ