Освой протокол итерации и генераторы, чтобы обрабатывать потоки данных лениво, при постоянном расходе памяти.
Открыть этот урок в Kodokonfor в Python - это всего лишь протокол: iter(obj) запрашивает итератор, затем вызывается next() до исключения StopIteration. Важно различать итерируемый объект (список, способный выдавать свежий итератор на каждом проходе) и итератор (курсор, который можно пройти только один раз). Понять этот протокол - значит понять, почему некоторые объекты «расходуются».
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
# next(iterator) would raise StopIterationФункция, содержащая yield, не выполняется при вызове: она возвращает генератор - итератор, тело которого работает по запросу. Каждый next() доводит выполнение до следующего yield, где оно приостанавливается вместе со всем локальным состоянием. Прямое следствие: память остаётся постоянной, каким бы ни был размер потока - ты производишь значения по одному, вместо того чтобы материализовать целый список.
def squares(limit):
n = 0
while n < limit:
yield n * n
n += 1
gen = squares(4)
print(next(gen)) # 0
print(list(gen)) # [1, 4, 9]Главная выгода на практике - конвейеры. Каждая ступень потребляет поток предыдущей и лениво производит свой; ничего не выполняется до финального потребления (list(), sum(), цикл for). Компромисс, о котором стоит помнить: список можно пройти несколько раз и нарезать срезами, генератор - нет; зато генератор обработает файл на 10 ГБ, не забив оперативную память. Генераторные выражения - (f(x) for x in xs if cond) - покрывают простые ступени без объявления функции.
lines = [" 12 ", "x", " 7", "", "30"]
def clean(rows):
for row in rows:
row = row.strip()
if row.isdigit():
yield int(row)
def keep_from(values, floor):
return (v for v in values if v >= floor)
pipeline = keep_from(clean(lines), 10)
print(list(pipeline)) # [12, 30]