Генератори

Модуль 1: Python Core
Рівень 8 , Лекція 3
Відкрита

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 байт
1
Опитування
Системні функції, рівень 8, лекція 3
Недоступний
Системні функції
Системні функції
Коментарі (2)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Дмитро Рівень 30
14 квітня 2025
Тема дійсно складна, по майже кожну задачу (особливо з осьанніх) розбирав з chatgpt, бо не розумів нащо воно взагалі Дуже вже прості приклади які віддалені від реального застосування
Andriy Trubchanin Рівень 23
16 лютого 2025
Цікаво скільки тут відвалюється людей. Хардкорно якось. Я зрозумів тему частково, але пішов далі )