Перейти до основного вмісту
Модуль 9 · Урок 4 із 436/36 у курсі~14 min
Модульні уроки (4/4)

Декоратори: функції, які змінюють функції

Декоратор — це функція, яка приймає іншу функцію та повертає a «прикрашена» його версія. Це потужний спосіб додати поведінку (реєстрація, синхронізація, кешування, повторна спроба, перевірка), не торкаючись тіла оригінальна функція.

Функції як цінності

У Python функції — це об’єкти: їх можна передавати, повертати, призначати.

Python
def saluta():
    print("ciao")

f = saluta   # NIENTE parentesi: assegno la funzione
f()          # 'ciao'

Мінімальний декоратор

Python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"chiamo {func.__name__}")
        risultato = func(*args, **kwargs)
        print(f"ho finito {func.__name__}")
        return risultato
    return wrapper

def somma(a, b):
    return a + b

somma = log(somma)   # ora somma è "wrapper"
somma(2, 3)
# chiamo somma
# ho finito somma
# 5

@decorator синтаксис

Призначення somma = log(somma) написано більш елегантно з @:

Python
@log
def somma(a, b):
    return a + b

Це точно те саме. @log вище визначення еквівалентно: "як тільки ця функція буде визначена, замініть її на log(function)".

*args, **kwargs: прийняти будь-який підпис

Python
def wrapper(*args, **kwargs):
    return func(*args, **kwargs)

*args збирає позиційні аргументи, **kwargs аргументи ключових слів. Вони дозволили ви пишете обгортки, які працюють з будь-якою функцією.

functools.wraps: зберегти метадані

Без wraps декорована функція втрачає __name__, __doc__, підказки:

Python
from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
    return wrapper

Приклад: @timeit

Python
from functools import wraps
import time

def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        risultato = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f"{func.__name__} ha impiegato {dt*1000:.2f} ms")
        return risultato
    return wrapper

@timeit
def somma_grande():
    return sum(range(10**6))

somma_grande()

Декоратори з stdlib

Python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n - 1) + fib(n - 2)

fib(100)  # istantaneo grazie alla cache
  • @lru_cache — запам'ятовує результати чистої функції.
  • @property — надає метод як атрибут (видно в модулі ООП).
  • @staticmethod, @classmethod — змінити спосіб передачі self.

Декоратори з аргументами

Декоратори також можуть приймати аргументи. Щоб реалізувати це, ви повинні написати три рівні вкладених функцій: зовнішня функція отримує аргументи декоратора, середня функція отримує декоровану функцію, а внутрішня функція (wrapper) отримує фактичні аргументи, що передаються декорованій функції під час викликів.

Спробуйте самі

вправи#python.m9.l4.e1
Спроби: 0Завантаження…

Напишіть декоратор `double_result`, який викликає декоровану функцію та повертає її результат * 2. Застосуйте його до функції `f(x)`, яка повертає x + 1. Обчисліть `f(10)`.

Завантаження редактора…
Показати підказку

Обгортка викликає функцію func і повертає результат, помножений на 2.

Рішення доступне після 3 спроб

Повторна вправа

вправи#python.m9.l4.e2
Спроби: 0Завантаження…

Використовуйте functools.lru_cache для прискорення рекурсивної функції fib(n). Обчислити fib(30) і призначити `r`. Оцініть `r`.

Завантаження редактора…
Показати підказку

@lru_cache(maxsize=None) над def.

Рішення доступне після 3 спроб

Додатковий виклик

вправи#python.m9.l4.e3
Спроби: 0Завантаження…

Define a decorator `double_result` that doubles (multiplies by 2) the return value of the decorated function. Apply the decorator to the function `def sum_nums(a, b): return a + b`. Finally, evaluate `sum_nums(3, 4)`.

Завантаження редактора…
Показати підказку

Обгортка викликає func(*args, **kwargs) і множить результат на 2.

Рішення доступне після 3 спроб