3.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):
pass # Обробляємо значення по одному
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)
Пояснення:
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 байт
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ