Встречается на 36% собеседований по Python

Что такое декораторы

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

Как это работает

В Python функции — это объекты первого класса, поэтому их можно передавать в другие функции и возвращать из них. Декоратор использует это свойство: он принимает функцию func, определяет внутри новую функцию-обёртку wrapper, которая добавляет логику до и после вызова func, и возвращает wrapper. Синтаксис @decorator перед определением функции эквивалентен присваиванию func = decorator(func).

Пример простого декоратора:

python
import functools

def log_decorator(func):
    @functools.wraps(func)  # сохраняет имя и docstring исходной функции
    def wrapper(*args, **kwargs):
        print(f"Вызов {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Результат: {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    """Складывает два числа"""
    return a + b

add(2, 3)  # Вызов add, Результат: 5

Здесь @log_decorator заменяет add на wrapper, который логирует вызов и результат.

Декораторы классов

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

Пример декоратора класса:

python
def add_repr(cls):
    def __repr__(self):
        return f"{cls.__name__}({self.__dict__})"
    cls.__repr__ = __repr__
    return cls

@add_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p)  # Point({'x': 1, 'y': 2})

Декораторы классов часто используют как альтернативу наследованию, когда нужно добавить функциональность нескольким несвязанным классам.

Паттерн «Декоратор»

В объектно-ориентированном программировании паттерн «Декоратор» позволяет динамически добавлять объектам новые обязанности, оборачивая их в классы-обёртки. В Python этот паттерн реализуется через композицию: класс-декоратор хранит ссылку на оборачиваемый объект и делегирует ему вызовы, добавляя своё поведение.

Пример из исходного ответа:

python
class TextTag:
    """Базовый класс для текстового тега"""
    def __init__(self, text):
        self._text = text

    def render(self):
        return self._text

class BoldWrapper(TextTag):
    """Оборачивает текст в <b>"""
    def __init__(self, wrapped):
        self._wrapped = wrapped

    def render(self):
        return f"<b>{self._wrapped.render()}</b>"

class ItalicWrapper(TextTag):
    """Оборачивает текст в <i>"""
    def __init__(self, wrapped):
        self._wrapped = wrapped

    def render(self):
        return f"<i>{self._wrapped.render()}</i>"

simple_hello = TextTag("hello, world!")
special_hello = ItalicWrapper(BoldWrapper(simple_hello))
print(simple_hello.render())  # hello, world!
print(special_hello.render())  # <i><b>hello, world!</b></i>

Здесь BoldWrapper и ItalicWrapper — декораторы, которые добавляют HTML-теги к тексту, не изменяя базовый класс.

Подводные камни

  • При использовании декораторов без functools.wraps теряются метаданные исходной функции (__name__, __doc__), что затрудняет отладку.
  • Декораторы выполняются при определении функции, а не при её вызове. Это важно учитывать при использовании внешних ресурсов.
  • Если декоратор принимает аргументы, нужен дополнительный уровень вложенности: @decorator(arg) возвращает декоратор, который принимает функцию.
  • Декораторы классов могут изменять класс неожиданным образом, если неаккуратно модифицировать его атрибуты.

Коротко

  • Декоратор — функция, принимающая функцию или класс и возвращающая расширенную версию.
  • Синтаксис @decorator — это сахар для func = decorator(func).
  • Декораторы бывают для функций, методов и классов.
  • Паттерн «Декоратор» реализуется через композицию и позволяет добавлять поведение без наследования.
  • Используйте functools.wraps, чтобы сохранять метаданные исходной функции.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы