Ітератори. генератори
📌 Посилання на тему
- Acode - Ітератори в Python
- Acode - Генератори в Python
- Iterators, generators and decorators
- PyDocs
- REalPython - How to Use Generators and yield in Python
📌 Ітератори (Iterators)
Ітератор — це об’єкт, який є:
- об'єктом-механізмом перебору
- має метод
__iter__()
, котрий повертає сам екземпляр ітератора (self) - має метод
__next__()
- поступово повертає елементи з колекції по одному, при
- кожному виклику методу
__next__()
або функції next(); - в циклі for
- кожному виклику методу
-
пам'ятаю на якому етапі перебору він зараз
-
після завершення викликів викликається виключення StopIteration
Протокол: щоб клас був ітератором, має реалізувати два методи:
-
__iter__()
— повертає сам об’єкт ітератора; -
__next__()
— повертає наступний елемент або піднімає StopIteration, коли елементів немає
class Counter(object):
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
'Returns itself as an iterator object'
return self
def __next__(self):
'Returns the next value till current is lower than high'
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
c = Counter(1, 20)
print(next(c)) # вертає 1
print(next(c)) # вертає 2
print(next(c)) # вертає 3
#на 221-й виклик next(c) викине виключення StopIteration
Переваги ітератора
- Лінива (lazy) обробка даних — елементи обробляються по мірі виклику, економиться пам’ять
- Можливість створення власної логіки ітерації за допомогою класів.
📌 Ітерабельні об'єкти (iterable objects)
Ітерабельні об’єкти:
- має дані для перебору
- не має інформацію (не пам'ятає), на відміну від ітератора, на якому етапі є перебір
- не має методу
__next__()
- може бути реалізований метод
__get_item__
- має метод,
__iter__()
, але на відміну від ітераторів, не повертає сам себе, а повертає готовий вже реалізований ітератор в залежності від типу ітерабельного об'єкта (список, кортеж, множина, рядок, словник) - кожен раз, коли потрібна ітерація по ітерабельному об'єкту відбувається повернення нового екземпляру ітератора цього об'єкта через метод
__iter__()
- виклик iter(obj) повертає ітератор для нього.
- Цикл for робить це автоматично
📌 Генератори (Generators)
Генератор — це спеціальний тип ітератора, створений за допомогою функції з ключовим словом yield.
- Коли така функція викликається, вона не виконується одразу, а повертає об’єкт-генератор
- якщо виконати функцію next() для отриманого генератора, починається власне виконання коду в середині функції до першого
yield
, який повертає певне значення й призупиняє функцію, зберігаючи її локальний стан. Наступні виклики next() продовжують виконання від місця паузи - замість виклику функції next() для генератора можна викликати в нього метод
__next__()
- для економії пам'яті при обробці даних (напр., двох масивів, читання з файлу і т.д.) можна повертати не самнабір даних, а ітератор (генератор) для отримання його. Він займає значно менше місця.
- для отримання даних відповідно використати генератор колекцій або цикл for, або функцію-конструктор колекції...
- бувають
- генератори-функції
- генератори-вирази
def my_generator():
print('High, I am generator')
yield 1
yield 2
yield 3
# на цьому етапі змінні повертається генератор, ніякої логіки в середині функції не виконується
my_g = my_generator()
# виводиться прінт повідомлення High, I am generator, а також прінт поверненого першого знаячення 1
print(next(my_g))
# прінт поверненого значення 2
print(next(my_g))
# як альтернатива функції next(), прінт поверненого значення 3
print(my_g.__next__())
# викидає виключення StopIteration
print(next(my_g))
* Виклик my_generator() повертає генератор в змінну my_g,
* next(my_g) або my_g.__next__() повертає значення по черзі
Генерація Фібоначчі:
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
Повертає нескінченну послідовність ефективно, без витрат пам'яті для всієї послідовності одночасно
Як працює yield
-
Коли Python бачить у функції yield, він не виконує її як звичайну функцію, а компілює у generator function.
-
При виклику такої функції код не запускається одразу, замість цього створюється generator object.
-
Під час першого виклику next(generator) виконання йде до першого yield:
- Значення після yield віддається викликачеві.
- Виконання призупиняється — зберігаються:
- Локальні змінні
- Позиція в коді
- Стек викликів
- При наступному next() виконання відновлюється з місця, де воно зупинилося.
Особливості методу yield:
- Призупиняє виконання функції, зберігає стан (локальні змінні, стек, позицію) — подібно до корутин
- На відміну від return, yield не завершує функцію, а дозволяє продовжити роботу з того самого місця при наступному виклику
📌 Ітератори vs Генератори — порівняння
Характеристика | Ітератор | Генератор |
---|---|---|
Реалізація | Клас з __iter__() і __next__() |
Функція з yield |
Пам’ять | Може зберігати весь стан або дані | Лінива генерація, економія пам’яті |
Зручність | Більше коду, ручне управління станом | Менше коду, автоматично зберігається стан |
Взаємовідношення | Не обов’язково генератор | Кожен генератор — це ітератор |
Статус | iterator |
generator (підклас Iterator) ([DataFlair][1]) |
- Генератори — це зручні й ефективні реалізації ітераторів, створені автоматично Python-ом через функції з yield
- Ітератори створюються руками для більш складної логіки й контролю.
📌 Генераторні вирази (Generator Expressions)
- Схожі на генератори списків (list comprehensions), але в круглих дужках;
- Вираз генератора не створює відразу готовий набір значень, а повертає об’єкт генератора, який:
- не зберігає всі результати в пам’яті
- видає значення поступово, коли ти їх запитуєш
- Отримати значення генератора можна:
- Через цикл
for
- Через функцію
next()
або метод генератора.__next__()
- Одноразово перетворивши на іншу структуру
- Через цикл
- ⚠ Важливий момент - після того, як генератор "видав" всі значення, він вичерпується — повторно пройтися ним не вийде без створення нового.
Отримати значення Через цикл
for
g = (x**2 for x in range(5))
print(g)
# <generator object <genexpr> at 0x...>
for val in g:
print(val)
Отримати значення Через функцію
next()
або метод генератора.__next__()
squares = (n * n for n in range(4))
print(squares) # <generator object <genexpr> at 0x000002A8BAC8BAC0>
print(next(squares)) # 0
print(squares.__next__()) # 1
print(next(squares)) # 4
print(squares.__next__()) # 9
print(next(squares)) # exception StopIteration
Отримати значення Через функцію
next()
або метод генератора.__next__()
g = (x**2 for x in range(5))
print(list(g)) # [0, 1, 4, 9, 16]
print(list(g)) # [] - пустий список, бо генератор вже не повертає жодних значень, він вже відпрацьований