Декораторы: функции над функциями
Декоратор — функция, которая принимает функцию и возвращает новую, добавляя поведение без правки исходного кода. Соберём декоратор с нуля: от замыкания и синтаксиса @ до functools.wraps, декораторов для методов и декораторов с параметрами.
Редакция Питоники
В Python функция — обычный объект: её можно положить в переменную, передать аргументом и вернуть из другой функции. Вы уже пользовались этим не замечая: sorted(data, key=len) получает функцию len, а @property и @staticmethod из прошлых уроков — декораторы. Теперь соберём свой. Сначала — фундамент: функция, возвращающая функцию, которая помнит окружение (замыкание):
- sorted(data, key=len) — функция len передаётся как обычный аргумент;
- объекты dict.get, str.upper — тоже значения: их можно передавать и вызывать позже;
- @property, @staticmethod, @functools.cache — декораторы, которыми вы уже пользовались как готовыми деталями.
Функция как значение и замыкание
def make_greeting(greeting):
def greet(name):
return f"{greeting}, {name}!"
return greet
hello = make_greeting("Привет")
bye = make_greeting("Пока")
print(hello("Анна"))
print(bye("Пётр"))
Привет, Анна! Пока, Пётр!
def shout(fn):
def wrapper():
return fn().upper() + "!"
return wrapper
def greet():
return "привет"
loud = shout(greet)
print(loud())
@shout
def farewell():
return "удачи"
print(farewell())
ПРИВЕТ! УДАЧИ!
functools.wraps: сохранить имя и docstring
import functools
def logged(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
result = fn(*args, **kwargs)
print(f"вызвана {fn.__name__}{args} -> {result}")
return result
return wrapper
@logged
def add(a, b):
"""Складывает два числа."""
return a + b
add(2, 3)
print(add.__name__)
print(add.__doc__)
вызвана add(2, 3) -> 5 add Складывает два числа.
Декоратор метода и декоратор с параметрами
def positive(fn):
def wrapper(self, value):
if value < 0:
raise ValueError("сумма должна быть неотрицательной")
return fn(self, value)
return wrapper
class Account:
def __init__(self):
self.balance = 0
@positive
def deposit(self, value):
self.balance += value
acc = Account()
acc.deposit(100)
acc.deposit(50)
print(acc.balance)
try:
acc.deposit(-10)
except ValueError as e:
print(e)
150 сумма должна быть неотрицательной
import functools
def repeat(times):
def deco(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
for _ in range(times):
result = fn(*args, **kwargs)
return result
return wrapper
return deco
@repeat(times=3)
def beep():
print("бип")
beep()
бип бип бип
Сначала предскажи ответ в голове — это главный навык программиста.
def decorate(fn):
def wrapper():
return "[" + fn() + "]"
return wrapper
@decorate
def hello():
return "py"
print(hello())
def double(fn):
def wrapper(*args):
return fn(*args) * 2
return wrapper
@double
def square(x):
return x * x
print(square(5))
import functools
def announce(fn):
@functools.wraps(fn)
def wrapper(*args):
print(f"-> {fn.__name__}")
return fn(*args)
return wrapper
@announce
def mul(a, b):
return a * b
print(mul(4, 6))
1. Что делает запись @shout над функцией farewell?
2. Что такое замыкание?
3. Зачем декоратору @functools.wraps(fn) над wrapper?
4. Почему в wrapper декоратора метода первым параметром стоит self?
5. Чем декоратор с параметрами отличается от обычного?
6. Два декоратора применены в порядке @a над @b. Чему эквивалентно описание функции f?
Напишите декоратор announce: перед вызовом декорированной функции он печатает строку вида «Вызываю add», затем возвращает результат функции. Используйте functools.wraps. Примените его к функции add(a, b), возвращающей сумму, и вызовите add(20, 22).
Что такое декоратор в Python простыми словами?
Это функция, которая принимает вашу функцию и возвращает обёртку вокруг неё: логирование, проверку аргументов, кэширование — без правки исходного кода. Запись @deco над функцией — сокращение для func = deco(func).
Урок 17: декораторыЗачем в декораторе functools.wraps?
Без него декорированная функция теряет имя и docstring: __name__ становится wrapper, а __doc__ — None, что ломает help(), отладку и документацию. @functools.wraps(fn) копирует метаданные исходной функции на обёртку — ставьте его первой строкой в каждом декораторе.
Как написать декоратор с параметрами?
Добавьте ещё один уровень: функция принимает параметры (например, repeat(times)) и возвращает обычный декоратор deco, который возвращает wrapper. Запись @repeat(times=3) сначала вычисляет repeat(times=3), и вернувшийся декоратор оборачивает функцию.
Можно ли декорировать методы класса?
Да: wrapper первым параметром принимает self, потому что после @deco имя метода указывает на wrapper, и вызов obj.method(x) передаёт ему obj как первый аргумент. Так работают, например, проверки аргументов и кэширование на уровне метода — как positive для deposit из урока.
Понравился урок? Сошлитесь на него
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 16
@dataclass: классы без бойлерплейта
Декоратор @dataclass пишет за вас __init__, __repr__ и __eq__ по описанным полям класса. Разберём значения по умолчанию, field(default_factory) для списков, frozen=True для неизменяемых объектов и __post_init__ для производных полей.
ООП · Урок 18
Множественное наследование и миксины
Python разрешает классу нескольких родителей — и сразу отвечает на вопрос «чей метод наследовать» через линейный порядок MRO. Разберём миксины как безопасный способ множественного наследования, алмазную проблему и кооперативный super().
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 19
Мини-клиент API на функциях
URL, разбор ответа, фильтр, файл — каждый шаг уже умещается в одну функцию. Собираем их в клиент, который читается как сценарий.
python клиент api функциифункции python для обработки json
asyncio · Урок 2
Первая корутина: async def и await
async def — функция с паузами, await — «дождись результата». Звучит просто, пока не выясняется, что вызов async-функции не выполняет её тело. Разбираем оба ключевых слова на живом коде.
async функция pythonасинхронные функции python
ООП · Урок 1
Классы в Python: первый объект
Класс — чертёж, объект — экземпляр по этому чертежу. Создаём первый класс, вызываем методы и убеждаемся: в Python объектом является всё, включая функции.
class python синтаксисtype python функция