Вопрос с собеседования · Python

Что такое декоратор и как его написать?

JuniorФункции
Ещё спрашивают так:
  • Как работают декораторы в Python?
  • Напишите декоратор, который замеряет время выполнения функции
  • Зачем нужен functools.wraps?
Коротко

Декоратор — функция, которая принимает функцию и возвращает новую; @dec — сахар для f = dec(f).

  • Обёртка внутри — замыкание, вызывает исходную через *args, **kwargs
  • functools.wraps сохраняет имя и docstring исходной функции

А ещё Покажите декоратор с параметрами — это три уровня вложенности

Подробно

Функции в Python — объекты: их можно передавать и возвращать. Декоратор этим пользуется: получает функцию, определяет внутри обёртку, которая что-то делает до и после вызова, и возвращает обёртку.

Запись

@timer
def work(): ...

полностью эквивалентна work = timer(work) и выполняется один раз — при определении функции.

Обёртка — замыкание: она помнит исходную функцию из внешней области. Чтобы подходить к любой функции, обёртка принимает *args, **kwargs и передаёт их дальше, а результат возвращает.

Без @functools.wraps(func) у обёртки будет имя wrapper и пустой docstring — это ломает логи, отладку и документацию.

Декоратор с параметрами (@retry(3)) — это функция, которая возвращает декоратор: снаружи параметры, внутри декоратор, ещё глубже обёртка.

Примеры из жизни: @property, @staticmethod, @functools.lru_cache, маршруты во Flask и FastAPI (@app.get("/")), проверка прав, логирование, повторные попытки.

Пример

import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            print(f"{func.__name__}: {time.perf_counter() - start:.3f} с")
    return wrapper

def retry(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if attempt == times - 1:
                        raise
        return wrapper
    return decorator

@timer
@retry(3)
def fetch():
    ...

Что хотят услышать

  • Определение через «функция принимает и возвращает функцию».
  • Эквивалентность @dec и f = dec(f), момент применения.
  • Рабочий код обёртки с *args, **kwargs и возвратом результата.
  • functools.wraps и декоратор с параметрами.

Частые ошибки

  • Забыть return результата исходной функции в обёртке.
  • Вызвать функцию при декорировании: return wrapper() вместо return wrapper.
  • Не знать, что порядок нескольких декораторов — снизу вверх.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Декоратор вызывается при каждом вызове функции, а не при её определении
  • ✓Функция, принимающая функцию и возвращающая новую; @dec — сахар для f = dec(f)
  • ✗Декоратор меняет байткод исходной функции
  • ✗Декоратор нельзя применить к классу, только к функции

Что спросят дальше

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования