Якщо ви коли-небудь дивились на Python-код із загадковими символами @ перед функціями і думали: “Що це за магія?”, то ви не самотні. Python декоратори — це одна з найпотужніших, але часто недооцінених можливостей мови для початківців. На практиці декоратори часто допомагають скоротити кількість дубльованого коду та зробити проєкти чистішими й простішими в підтримці.
У світі аналітики даних та AI, де Python є однією з провідних мов програмування, декоратори є дуже корисним інструментом. Від логування ML-моделей до обробки помилок при завантаженні даних — вони допомагають дата-інженерам та data scientists фокусуватися на бізнес-логіці, а не на технічних деталях.
Проста аналогія: декоратори як обгортка для подарунка
Уявіть, що у вас є подарунок (ваша функція), який ви хочете красиво упакувати. Декоратор — це обгортка. Вона не змінює внутрішню логіку самої функції, але додає їй нову поведінку: наприклад, логування, вимірювання часу виконання або обробку помилок.
У Python функції є об’єктами першого класу (first-class citizens). Це означає, що їх можна передавати як аргументи іншим функціям. Декоратори працюють саме за цим принципом — вони “обгортають” існуючі функції, додаючи їм нову поведінку без зміни їхньої основної бізнес-логіки.
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Щось відбувається ДО виклику функції")
result = func(*args, **kwargs)
print("Щось відбувається ПІСЛЯ виклику функції")
return result
return wrapper
# Символ @ — це синтаксичне скорочення, яке застосовує декоратор до функції
@my_decorator
def greet(name):
print(f"Привіт, {name}!")
greet("Анна")Чому декоратори критично важливі в Data Science та AI
Декоратори — це один із прикладів функціонального підходу в Python. Вони добре допомагають вирішувати кілька архітектурних задач в аналітиці:
- Масштабованість: Якщо завтра вам знадобиться додати відправку метрик або додаткове логування для всіх ваших ML-моделей, це можна буде зробити в одному декораторі, не змінюючи десятки окремих функцій.
- Принцип DRY (Don’t Repeat Yourself): Замість копіювання однакового коду для вимірювання часу виконання кожної функції, ви пишете логіку один раз.
- Separation of Concerns: Ви відділяєте чисту математику (або трансформацію даних) від інфраструктурних задач (логування, авторизація, кешування).
Практичні приклади декораторів для аналітики
1. Вимірювання часу виконання (Профайлінг)
Під час обробки великих датасетів (наприклад, у Pandas) завжди потрібно розуміти, який етап працює найдовше.
import time
from functools import wraps
def time_profiler(func):
@wraps(func) # Зберігає ім'я та документацію оригінальної функції
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} виконалась за {end - start:.4f} сек")
return result
return wrapper
@time_profiler
def process_large_dataset(df):
# Імітація важкої обробки даних
return df.groupby('category').sum()2. Автоматичний повтор при помилках (Retry Pattern)
В інженерії даних часто доводиться звертатися до зовнішніх API, які можуть тимчасово не відповідати. Замість того, щоб писати блоки try/except і цикли в кожній функції, ми виносимо це в декоратор.
import time
from functools import wraps
def retry_on_failure(retries=3, delay=2, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(1, retries + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_error = e
if attempt < retries:
print(f"Спроба {attempt} провалилася: {e}. Повтор через {delay} сек...")
time.sleep(delay)
raise RuntimeError(
f"Функція {func.__name__} не виконалася після {retries} спроб."
) from last_error
return wrapper
return decorator
@retry_on_failure(retries=3, delay=5)
def fetch_financial_data(api_url):
# Тут має бути код запиту до нестабільного API
pass3. Валідація даних перед обробкою
Декоратор може слугувати “охоронцем”, перевіряючи, чи відповідає переданий DataFrame очікуваному формату, перш ніж запускати важкі обчислення. Якщо відсутні потрібні колонки — функція навіть не почне роботу, відразу викинувши зрозумілу помилку.
Advanced використання: класи та асинхронність
Асинхронні декоратори
З розвитком фреймворків на кшталт FastAPI, все більше Python-коду стає асинхронним. Якщо декоратор написаний без урахування асинхронного коду, він може некоректно працювати з async def функціями. Тому для асинхронного коду обгортка також повинна бути асинхронною і використовувати await під час виклику оригінальної функції::
import time
from functools import wraps
def async_timer(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = time.perf_counter()
result = await func(*args, **kwargs)
print(f"Async {func.__name__}: {time.perf_counter() - start:.4f}s")
return result
return wrapper(Детальніше про асинхронне програмування можна прочитати в нашій статті про конкурентність в Python)
Комбінування (чейн) декораторів
До однієї функції можна застосувати одразу кілька декораторів. Важливо пам’ятати, що вони застосовуються знизу вгору: спочатку спрацьовує той, що розташований найближче до функції:
@time_profiler # Виконається другим
@retry_on_failure(retries=2) # Виконається першим
def analyze_prices(df):
return df.describe()Типові помилки та як їх уникнути
- Втрата метаданих: Якщо ви створюєте декоратор без
from functools import wraps, ваша задекорована функція втратить своє справжнє ім’я (__name__) та документацію (__doc__). Вона буде називатисяwrapper. Завжди використовуйте@wraps. - Втрата результату (Return): Дуже часта помилка початківців — забути написати
return resultу функції-обгортці. Через це ваша функція відпрацює, але повернеNone. - Зловживання декораторами: Якщо логіка декоратора занадто специфічна й використовується лише в одному місці, іноді простіше обійтися без нього. Декоратори додають невеликий оверхед і можуть ускладнювати читання стеку викликів під час дебагу.
Підсумок та наступні кроки
Декоратори — це один з інструментів, який допомагає перейти від коду, що просто працює, до коду, який легше підтримувати, розширювати та масштабувати.
Якщо ви хочете систематично опанувати не лише декоратори, а й контекстні менеджери, генератори, ООП та інші просунуті можливості мови, рекомендуємо курс «Програмування на Python». За 2 місяці інтенсивного навчання ви вийдете на рівень впевненого написання production-ready рішень.
Пам’ятайте: хороший код — це код, який економить час вам та вашій команді, а Python-декоратори — один із корисних інструментів для досягнення цієї мети.