← Назад к вопросам

Чем отличается итератор и генератор?

1.0 Junior🔥 201 комментариев
#Python

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Итератор vs Генератор в Python

Отличный базовый вопрос про Python, который очень актуален для Data Engineer, работающего с большими объёмами данных. Разберу различия.

Основные определения

Итератор — это объект, который имеет методы __iter__() и __next__():

  • __iter__() — возвращает сам итератор
  • __next__() — возвращает следующий элемент

Генератор — это функция, которая использует yield для возврата значений по одному. Генератор — это тип итератора (но не наоборот).

# Генератор — это частный случай итератора
Генератор ⊂ Итератор

Пример 1: Итератор (класс)

# Создаём итератор вручную
class CountUp:
    def __init__(self, max):
        self.max = max
        self.current = 0
    
    def __iter__(self):  # Метод iter
        return self
    
    def __next__(self):  # Метод next
        if self.current < self.max:
            self.current += 1
            return self.current
        else:
            raise StopIteration  # Обязательно для завершения

# Использование
count = CountUp(3)
for num in count:
    print(num)  # 1, 2, 3

# Или вручную
count = CountUp(3)
print(next(count))  # 1
print(next(count))  # 2
print(next(count))  # 3
print(next(count))  # StopIteration Exception

Пример 2: Генератор (функция с yield)

# То же самое, но генератором (проще!)
def count_up(max):
    current = 0
    while current < max:
        current += 1
        yield current  # Возвращаем значение и паузируем

# Использование (идентично итератору выше)
for num in count_up(3):
    print(num)  # 1, 2, 3

# Или вручную
gen = count_up(3)
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
print(next(gen))  # StopIteration (автоматично!)

Сравнительная таблица

АспектИтераторГенератор
Что этоОбъект классаФункция
Созданиеclass MyIter: __iter__(), __next__()def my_gen(): yield
МетодыНужно определить __iter__() и __next__()Автоматические
СостояниеВ переменных объекта (self.x)В локальных переменных функции
КодБольше кода (~10-15 строк)Меньше кода (~3-5 строк)
ПамятьДержит состояние в объектеБолее эффективно
ПроизводительностьБыстрее немногоЧуть медленнее (функция)

Пример 3: Практический кейс для Data Engineer

Задача: Читаем CSV файл с 1 миллиардом строк. Не можем загрузить всё в память.

Плохо (без итератора):

# Это упадёт с MemoryError!
import pandas as pd

df = pd.read_csv('huge_file.csv')  # Пытаемся загрузить 1 млрд строк
# MemoryError: Unable to allocate 500 GB for data

Хорошо (с итератором):

# Способ 1: Используем read_csv с chunksize (это итератор!)
import pandas as pd

for chunk in pd.read_csv('huge_file.csv', chunksize=10000):
    # Обрабатываем по 10K строк за раз
    print(f"Processing {len(chunk)} rows")
    # chunk — это DataFrame с 10K строк
    # После loop — старый chunk удаляется из памяти

print("Done! Обработали все данные без OutOfMemory")

Хорошо (с генератором):

# Способ 2: Пишем свой генератор
def read_large_csv(filename, chunksize=10000):
    with open(filename) as f:
        chunk = []
        for line in f:
            chunk.append(line.strip().split(','))
            if len(chunk) == chunksize:
                yield chunk  # Отправляем 10K строк
                chunk = []   # Очищаем память
        if chunk:
            yield chunk  # Последний partial chunk

# Использование
for batch in read_large_csv('huge_file.csv', chunksize=10000):
    print(f"Processing batch of {len(batch)} rows")
    # Обрабатываем batch
    for row in batch:
        # process row
        pass

Пример 4: Внутренний механизм yield

Что происходит при yield:

def my_gen():
    print("Start")      # Выполняется при next() #1
    yield 1            # Возвращаем 1, паузируем здесь
    print("Middle")    # Выполняется при next() #2
    yield 2            # Возвращаем 2, паузируем здесь
    print("End")       # Выполняется при next() #3
    yield 3            # Возвращаем 3, паузируем здесь

gen = my_gen()
print("Created generator")
print(next(gen))     # Output: "Start" → returns 1
print(next(gen))     # Output: "Middle" → returns 2
print(next(gen))     # Output: "End" → returns 3
print(next(gen))     # StopIteration (функция закончена)

Вывод:

Created generator
Start
1
Middle
2
End
3

Ключевой момент: код в функции выполняется пошагово, не весь сразу!

Пример 5: Генератор выражений

Есть ещё более лёгкий способ — generator expression:

# Обычный список (съедает память)
numbers = [x for x in range(1000000)]  # Создаёт список в памяти, ~40 MB

# Generator expression (не съедает память)
numbers_gen = (x for x in range(1000000))  # Скобки вместо квадратных!

# Разница:
print(numbers[999999])      # Быстро (уже в памяти)
next(numbers_gen)           # Ленивое вычисление

# Generator expression занимает ~0 MB до первого использования

Пример 6: Цепочка генераторов

Для Data Engineering часто нужно цепочка обработок:

def read_csv(filename):
    """Читаем CSV построчно"""
    with open(filename) as f:
        for line in f:
            yield line.strip().split(',')

def filter_valid(rows):
    """Фильтруем невалидные строки"""
    for row in rows:
        if len(row) == 4:  # Ожидаем 4 колонки
            yield row

def parse_numbers(rows):
    """Преобразуем строки в числа"""
    for row in rows:
        try:
            yield [row[0], int(row[1]), float(row[2]), row[3]]
        except ValueError:
            pass  # Пропускаем строки с ошибками

def transform_to_dict(rows):
    """Преобразуем в словари"""
    for row in rows:
        yield {
            'id': row[0],
            'qty': row[1],
            'price': row[2],
            'name': row[3]
        }

# Pipeline
data = read_csv('data.csv')
data = filter_valid(data)
data = parse_numbers(data)
data = transform_to_dict(data)

# Обработка (всё выполняется лениво!)
for item in data:
    print(f"Processing: {item['name']} (qty={item['qty']})")

# Память: только 1 строка в памяти в момент времени!

Пример 7: Бесконечный генератор

Генераторы хороши для бесконечных последовательностей:

# Бесконечный счётчик
def infinite_counter(start=0):
    n = start
    while True:
        yield n
        n += 1

# Использование (берём первые 5)
from itertools import islice
for num in islice(infinite_counter(100), 5):
    print(num)  # 100, 101, 102, 103, 104

# Итератор же требовал бы while True loop явно

Пример 8: Производительность итератор vs генератор

import time

# Итератор (класс)
class RangeIterator:
    def __init__(self, n):
        self.n = n
        self.i = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.i < self.n:
            self.i += 1
            return self.i
        raise StopIteration

# Генератор
def range_gen(n):
    for i in range(n):
        yield i

# Бенчмарк
n = 10_000_000

start = time.time()
for x in RangeIterator(n):
    pass
print(f"Iterator: {time.time() - start:.2f}s")

start = time.time()
for x in range_gen(n):
    pass
print(f"Generator: {time.time() - start:.2f}s")

start = time.time()
for x in range(n):
    pass
print(f"Built-in range: {time.time() - start:.2f}s")

# Примерные результаты:
# Iterator: 0.82s
# Generator: 0.95s
# Built-in range: 0.75s
# (встроенные функции быстрее)

Когда использовать?

Генератор когда:

  • ✅ Нужна простота (yield вместо next)
  • ✅ Большой объём данных (memory efficient)
  • ✅ Pipeline обработки данных
  • ✅ Бесконечные последовательности
  • ✅ Лёгкое преобразование

Итератор когда:

  • ✅ Нужна контроль (сложная логика состояния)
  • ✅ Несколько методов кроме next()
  • ✅ Нужна производительность (чуть быстрее)
  • ✅ Переиспользуемый класс

Best Practices для Data Engineer

  1. Используй генераторы по умолчанию — проще писать
  2. Для больших файлов — всегда chunked processing
  3. Pipeline с генераторами — очень efficient
  4. Профилируй память — убедись, что не растёт
  5. itertools — твой друг — islice, chain, groupby и т.д.
from itertools import islice, chain, groupby

# Примеры
first_5 = islice(my_gen, 5)           # Первые 5 элементов
chained = chain(gen1, gen2, gen3)      # Объедини генераторы
by_key = groupby(sorted_data, key)     # Группируй

Заключение

Главное различие:

  • Итератор — это объект с методами __iter__() и __next__()
  • Генератор — это функция с yield, которая автоматически создаёт итератор

Для Data Engineer: генераторы — это твой основной инструмент для работы с большими данными. Они экономят память, упрощают код и делают его readable. Используй их везде, где обрабатываешь потоковые или большие данные.

Чем отличается итератор и генератор? | PrepBro