Пояснення декораторів Python: від @property до повторної логіки за 15 хвилин (2026)
Декоратори — це функція, яка відокремлює «знає Python» від «вільно читає Python». Вони виглядають чарівно, але вони механічні: @decorator у функції — це точно f = decorator(f). Ось і все. Все інше в цьому підручнику полягає в тому, що одна лінія застосована в різних формах.
Теорія однієї лінії
Коли ви пишете:
Python перетворює його на:
Ось і вся механіка. @decorator_name над def запускає def_name = decorator_name(def_name) після визначення. Якщо ви засвоїте це, декоратори назавжди перестануть бути загадковими.
Написання вашого першого декоратора (60 секунд)
Декоратор — це будь-який виклик, який приймає функцію та повертає її. Типова форма:
*args, **kwargs в обгортці є захисним шаблоном — він дозволяє декоратору працювати з будь-якою функцією незалежно від підпису. Пропустіть це, і ви отримаєте TypeError: wrapper() приймає 0 позиційних аргументів, але було надано 2 у той момент, коли хтось прикрашає функцію параметрами.
Виправлення @functools.wraps
Одна тонка помилка: після декорування add.__name__ є "wrapper", а не "add"`. Рядки документації та підказки типу також зникають. Виправлення полягає в одному імпорті:
Правило: кожен декоратор, який ви пишете, має використовувати `@functools.wraps(fn)`. Він коштує один рядок і запобігає цілій категорії проблем з налагодженням (самоаналіз, кріплення pytest, форматувальники logging, pickle — усі без нього ламаються).
Чотири декоратори, які ви побачите в кожній кодовій базі
1. @property — обчислені атрибути
@property робить метод викликаним як якщо б це був атрибут. Ідеально підходить для похідних значень лише для читання. Існує також @area.setter для шляху запису, але 90% використання @property призначено лише для читання.
2. @staticmethod і @classmethod
@classmethod отримує cls (самий клас) і використовується для альтернативних конструкторів. @staticmethod не отримує нічого особливого і є простою функцією, яка випадково живе в просторі імен класу.
3. @functools.cache — запам'ятовування
Один із найпотужніших декораторів stdlib. Автоматично запам’ятовує функцію на основі її аргументів. Вимагає, щоб усі аргументи були хешованими (без списків/диктів як аргументів — використовуйте кортежі).
4. @dataclasses.dataclass — усунення шаблону
@dataclass читає анотації типу на рівні класу та автоматично генерує __init__, __repr__, __eq__ та (із frozen=True) __hash__. Це найбільше покращення якості життя в Python 3.7+.
Написання декоратора @retry (реальний приклад)
Декоратор, який повторює спробу функції в разі помилки з експоненціальним відстрочкою:
Зверніть увагу на три вкладені функції: retry приймає параметри конфігурації та повертає decorator; decorator приймає функцію та повертає wrapper; wrapper запускає цикл повторення. Цей тришаровий шаблон виглядає як будь-який декоратор з аргументами — запам’ятовуйте форму, а не конкретний приклад.
Класові декоратори (рідкісні, але корисні)
Декоратор — це будь-яке покликання. Класи з __call__ також працюють:
Використовуйте, коли декоратору потрібно утримувати стан, який переживає один виклик (лічильник, кеш, відро обмеження швидкості).
Поширені підводні камені
1. Забути `@functools.wraps` — самоспостереження переривається тихо.
2. Змінні аргументи за замовчуванням у самому декораторі — тут також трапляються помилки класичної def wrapper(x, cache=[]).
3. Декорування методів без прийняття `self` — пам'ятайте, що self є лише позиційним аргументом; *args, **kwargs вловлює це добре.
4. Порядок має значення під час укладання: @a\n@b\ndef f() — це f = a(b(f)). Читайте стеки знизу вгору.
Практика
Напишіть декоратор @timing, який друкує, скільки часу потрібно для виконання обгорнутої функції, а потім застосуйте його до функції, яка обчислює sum(range(10_000_000)). Весь декоратор складається з 8 рядків із functools.wraps + time.perf_counter.
Коли ви зрозумієте, що @x — це f = x(f), декоратори стають однією з найпродуктивніших функцій Python. Вони всюди в реальних кодових базах: маршрути Flask/FastAPI (@app.route), фікстури pytest (@pytest.fixture), перегляди Django (@login_required), валідатори Pydantic (@field_validator). Якщо навчитися писати їх самостійно, ви зможете прочитати кожен із них.
Наступний крок: 10 практичних уроків декоратора — створіть @retry, @memoise, @rate_limit і @requires_auth з нуля за допомогою справжнього Python, запущеного у вашому браузері.