В чем разница между итератором и декоратором в Python?

Ответ

Это совершенно разные концепции, решающие разные задачи.

Итератор (Iterator) — это объект, который позволяет последовательно обходить элементы коллекции (или потока данных). Он реализует протокол итерации через методы __iter__() (возвращает сам итератор) и __next__() (возвращает следующий элемент или вызывает StopIteration).

# Создание собственного итератора для генерации последовательности квадратов
class SquareIterator:
    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
        result = self.current ** 2
        self.current += 1
        return result

# Использование
for num in SquareIterator(5):
    print(num)  # Вывод: 0, 1, 4, 9, 16

# Итераторы лежат в основе генераторов
squares_gen = (x**2 for x in range(5))  # generator expression

Декоратор (Decorator) — это функция, которая принимает другую функцию (или класс) и возвращает новую, модифицированную функцию, не изменяя исходный код. Это паттерн для добавления сквозной функциональности (логирование, кэширование, проверка прав).

import time
import functools

# Декоратор для замера времени выполнения и кэширования результата
def timer_and_cache(func):
    cache = {}

    @functools.wraps(func)  # Сохраняем метаданные оригинальной функции
    def wrapper(*args, **kwargs):
        # Ключ для кэша — аргументы функции
        cache_key = (args, tuple(kwargs.items()))

        if cache_key in cache:
            print(f"Возвращаем результат из кэша для {func.__name__}")
            return cache[cache_key]

        print(f"Запускаем {func.__name__}...")
        start_time = time.perf_counter()
        result = func(*args, **kwargs)  # Вызов оригинальной функции
        elapsed = time.perf_counter() - start_time

        cache[cache_key] = result
        print(f"{func.__name__} выполнилась за {elapsed:.4f} сек.")
        return result

    return wrapper

# Применение декоратора
@timer_and_cache
def expensive_computation(n):
    time.sleep(0.5)  # Имитация долгого вычисления
    return n * n

print(expensive_computation(5))  # Запускает вычисление
print(expensive_computation(5))  # Возвращает из кэша

Итог: Итератор — это про обход данных. Декоратор — про модификацию поведения функций.

Ответ 18+ 🔞

Слушай, давай разберём эту дичь, потому что народ постоянно их путает, а это, блядь, как сравнивать табуретку и микроволновку — обе в квартире есть, но задачи у них вообще разные, ёпта.

Итератор (Iterator) — это такой занудный тип, который умеет выдавать элементы по одному, пока они не кончатся. Представь, что у тебя мешок с картошкой, и ты можешь только одну достать, потом вторую, и так, пока мешок не опустеет. Вот итератор — это рука, которая лезет в этот мешок. У него два главных метода: __iter__() (он говорит "да, я тот самый, кто будет шарить") и __next__() (это когда он таки вытаскивает следующую картофелину, а если их нет — орёт StopIteration).

# Вот смотри, делаем своего итератора, который квадраты чисел выдаёт
class SquareIterator:
    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
        result = self.current ** 2  # Считаем квадрат
        self.current += 1  # Переползаем на следующее число
        return result

# Используем
for num in SquareIterator(5):
    print(num)  # Выведет: 0, 1, 4, 9, 16 — всё по порядку, красота!

# Кстати, генераторы — это итераторы в красивой упаковке, чтоб не писать классы
squares_gen = (x**2 for x in range(5))  # Та же фигня, но в одну строчку

Декоратор (Decorator) — это, блядь, хитрая жопа. Это не про обход данных, а про то, как обернуть одну функцию в другую, чтобы добавить какую-то фичу, даже не лезя в её исходники. Типа надеваешь на функцию куртку с капюшоном, и она теперь не мокнет. Логирование, замер времени, кэширование — вот это всё их работа.

import time
import functools

# Сделаем декоратор, который и время замеряет, и результат кэширует, чтоб два раза не считать
def timer_and_cache(func):
    cache = {}  # Тут будем хранить посчитанные результаты, типа память

    @functools.wraps(func)  # Это чтоб наша обёртка не сломала имя оригинальной функции
    def wrapper(*args, **kwargs):
        # Составляем ключ для кэша из аргументов — чтоб понимать, считали мы уже такое или нет
        cache_key = (args, tuple(kwargs.items()))

        if cache_key in cache:  # О, да мы это уже считали!
            print(f"Ага, достаём из кэша результат для {func.__name__}, не паримся")
            return cache[cache_key]

        print(f"Запускаем {func.__name__}... сейчас посчитаем, держись")
        start_time = time.perf_counter()
        result = func(*args, **kwargs)  # Вот тут вызывается оригинальная функция, её мы и оборачиваем
        elapsed = time.perf_counter() - start_time

        cache[cache_key] = result  # Запоминаем результат на будущее
        print(f"Функция {func.__name__} проторчала {elapsed:.4f} секунд.")
        return result

    return wrapper  # Возвращаем нашу обёрнутую функцию

# Вешаем декоратор на функцию
@timer_and_cache
def expensive_computation(n):
    time.sleep(0.5)  # Прикинься, что тут суперсложные вычисления
    return n * n

print(expensive_computation(5))  # Первый раз — считает, ждёт полсекунды
print(expensive_computation(5))  # Второй раз — охуенно, сразу из кэша выдаёт!

Короче, резюмирую: Итератор — это про то, как ходить по данным (по одному элементу, не сразу все). Декоратор — про то, как изменить поведение функции, не ковыряясь в её кишках. Совершенно разные вещи, просто оба слова на "тор" заканчиваются, вот народ и путает, ебать мои старые костыли.