Входячи в пайтон-розробку, кожен дев точно стикався з цим явищем, бо декоратори присутні у каждому, просто у каждому сучасному фреймворці Пайтона
То, що ж це таке?
Декоратори — функції, що обгортують іншу функцію, надаючи додаткову логіку або взагалі замінюючи її. Замість функцій можуть бути використані класи, але це вже зовсім інша історія 😅
Це, можна сказати, один з механізмів наслідування, але зі світу функціонального програмування. І так легко пишеться хороший код — жодного доступу до зміни внутрішньої поведінки, тільки розширення, а це один з принципів SOLID 🤓
Думаю саме тому декоратори стали такими популярними 😍
Приклади використання
Реєстрація функції як callback, для серверу чи бота
# https://fastapi.tiangolo.com/#example
@app.get('/items/{item_id}')
def read_item(item_id: int, q: Union[str, None] = None):
return {'item_id': item_id, 'q': q}
Просте кешування
# https://docs.python.org/3/library/functools.html#functools.lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
Промаркувати функцію для подальшого використання, не змінюючи її
(в цьому випадку у функції додається атрибут pytestmark)
# https://docs.pytest.org/en/7.1.x/how-to/skipping.html#skipping-test-functions
import sys
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function():
...
Заміна метода дескриптором
# https://docs.python.org/3/library/functions.html#property
class Parrot:
def __init__(self):
self._voltage = 100000
@property
def voltage(self):
"""Get the current voltage."""
return self._voltage
Що ж, пора й самому щось написати ? 😁
Приклад написання
def timeit(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = f(*args, **kwargs)
total_time = time.perf_counter() - start_time
print(
f'Function {f.__name__}, args: {args}, kwargs: {kwargs} '
f'took {total_time:.2f} seconds'
)
return result
return wrapper
Тут, ми водночас, використовуємо інший декоратор — @functools.wraps, який є каноном для функцій-обгорток, і дозволяє нам не змінювати назву оригінальної функції. Без нього, після створення функції з використанням декоратора, кінцевий користувач буде бачити в логах незрозумілу для нього функцію wrapper
Насправді, в декораторах немає нічого складного, якщо розуміти, що нижні два приклади є цілком ідентичними для Пайтону:
@timeit
def add(a, b):
return a + b
def add(a, b):
return a + b
add = timeit(add)
І тут єдине, що робить Пайтон — дає дуже зручний спосіб використання
У прикладі з timeit та lru_cache, додаткова логіка застосовується при виклику функції, і викликається оригінальна функція, без зміни аргументів. Не бачу особливого сенсу розбирати деталі, але якщо є такий запит, пишіть у коментарях 💬
Q&A
Якщо у вас все ще залишились запитання про декоратори у пайтоні, як чи навіщо їх писати — завжди готовий відповісти у коментарях
Усім добра та мирного неба над головою ❤️