И первое с чего нужно начать — итераторы нужны не ради экономия памяти, а они нужны ради контроля над чтением данных
Самый простой пример:
class DataStream:
def __init__(self, source):
self.source = source
self.pos = 0
def __iter__(self):
return self
def __next__(self):
if self.pos >= len(self.source):
raise StopIteration
value = self.source[self.pos]
self.pos += 1
return value
Такой объект читает данные по одному элементу и запоминает позицию
Т.е. состояние хранится внутри и повторно пройтись по данным нельзя
stream = DataStream([1, 2, 3])
list(stream) # [1, 2, 3]
list(stream) # []
По сути если объект можно читать сколько угодно раз — это скорее коллекция
А если можно прочитать только один раз — это поток
Итератор — это одноразовый поток
В целом можно сделать и через генератор:
def stream(data):
for x in data:
yield x
Но так стоит делать в следующих ситуациях:
- нет необходимости возвращаться назад
- нет внешних эффектов
- не нужно управление процессом чтения
Но как только появится задача вроде "продолжить с места последнего падения" или "повторить последний шаг" — с генератором может быть больше проблем, чем пользы
Как можно сохранить позицию, например:
class CheckpointStream(DataStream):
def checkpoint(self):
return self.pos
def restore(self, pos):
self.pos = pos
stream = CheckpointStream([100, 200, 300])
print(next(stream)) # 100
saved = stream.checkpoint()
print(next(stream)) # 200
stream.restore(saved)
print(next(stream)) # снова 200
Так работают пакетные обработки, конвейеры данных и повторные запуски после ошибок
А асинхронный вариант может выглядеть так:
class AsyncStream:
def __aiter__(self):
return self
async def __anext__(self):
...
# Дальше обычный цикл:
async for item in stream:
...
Если подытожить:
Генератор — удобный синтаксис
Итератор — инструмент управления
Если контроль над данными и позициями не нужен — не усложняйте код, можете просто использовать генератор
⭐️ Пост Guru Python:
Сергей Филичкин