1.1Kпросмотров
25.7%от подписчиков
4 марта 2026 г.
Score: 1.3K
🔄 Итераторы и генераторы: как for реально работает под капотом Все пишут for x in list, но мало кто знает, что за этим скрывается протокол итератора. Разберём магию: как объекты становятся итерируемыми, как написать свой итератор и почему yield экономит память лучше любого оптимизатора. ❓ Что на самом деле делает for Когда ты пишешь:
items = [1, 2, 3]
for item in items: print(item) Python делает примерно это:
iterator = iter(items) # получаем итератор
while True: try: item = next(iterator) # берём следующий элемент print(item) except StopIteration: # когда элементы кончились break
➡️ Любой объект можно скормить for, если у него есть методы iter() или getitem(). Но настоящая магия — в протоколе итератора. 🔍 Протокол итератора: два метода, которые всё решают Чтобы объект стал итерируемым, ему нужны:
- iter() — возвращает итератор (обычно self)
- next() — возвращает следующий элемент или бросает StopIteration
class Counter: def init(self, limit): self.limit = limit self.current = 0 def iter(self): return self # возвращаем себя как итератор def next(self): if self.current >= self.limit: raise StopIteration # сигнал, что всё self.current += 1 return self.current - 1 for x in Counter(5): print(x) # 0 1 2 3 4
➡️ Вот так for понимает, когда остановиться. Всё честно, без магии. ✅ Свой итератор для бесконечной последовательности Хочешь бесконечный счётчик? Легко:
class InfiniteCounter: def init(self, start=0): self.current = start def iter(self): return self def next(self): self.current += 1 return self.current - 1 counter = InfiniteCounter()
for i in counter: print(i) if i > 5: break
# 0 1 2 3 4 5
➡️ Бесконечность не предел. Главное — не забыть break, иначе уйдёшь в космос. 🧪 Проблема: писать классы каждый раз лень Согласись, тащить целый класс ради одного итератора — жирно. Тут на сцену выходят генераторы.
def counter_gen(limit): for i in range(limit): yield i for x in counter_gen(5): print(x) # 0 1 2 3 4
➡️ yield превращает функцию в генератор. При каждом вызове next() она выполняется до следующего yield и замораживает состояние. 🚀 Бесконечный генератор в одну строку
def infinite_gen(start=0): while True: yield start start += 1 gen = infinite_gen()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
➡️ Памяти занимает ровно столько, сколько нужно для хранения текущего состояния. Никаких гигантских списков. 📦 Почему генераторы экономят память: наглядный пример
import sys # Список
numbers_list = [x for x in range(1_000_000)]
print(sys.getsizeof(numbers_list)) # ~8 MB # Генератор
numbers_gen = (x for x in range(1_000_000))
print(sys.getsizeof(numbers_gen)) # ~112 bytes
➡️ Список хранит все миллион элементов в памяти. Генератор — только текущее состояние и ссылку на функцию. Разница в тысячи раз. 🔧 Реальный кейс: читаем гигабайтный файл
def read_large_file(file_path): with open(file_path, 'r') as f: for line in f: yield line.strip() # Используем
for line in read_large_file("huge_log.txt"): if "ERROR" in line: print(line)
➡️ Файл читается построчно, в памяти только одна строка. Можно обрабатывать терабайты логов на ноутбуке с 4GB RAM. ⚡️ Генераторные выражения: list comprehension без памяти
# Список (жадный)
squares_list = [xx for x in range(1000)] # Генератор (ленивый)
squares_gen = (xx for x in range(1000)) print(sum(squares_gen)) # можно посчитать сумму без создания списка
➡️ Круглые скобки вместо квадратных — и миллион элементов не жрут память. 🗣️ Запомни: for - это просто синтаксический сахар над iter() и next() с ловлей StopIteration. Генераторы с yield позволяют писать ленивые последовательности без классов.