Модульні уроки (4/4)
Декоратори: функції, які змінюють функції
Декоратор — це функція, яка приймає іншу функцію та повертає a «прикрашена» його версія. Це потужний спосіб додати поведінку (реєстрація, синхронізація, кешування, повторна спроба, перевірка), не торкаючись тіла оригінальна функція.
Функції як цінності
У Python функції — це об’єкти: їх можна передавати, повертати, призначати.
def saluta():
print("ciao")
f = saluta # NIENTE parentesi: assegno la funzione
f() # 'ciao'Мінімальний декоратор
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) написано більш елегантно з @:
@log
def somma(a, b):
return a + bЦе точно те саме. @log вище визначення еквівалентно:
"як тільки ця функція буде визначена, замініть її на log(function)".
*args, **kwargs: прийняти будь-який підпис
def wrapper(*args, **kwargs):
return func(*args, **kwargs)*args збирає позиційні аргументи, **kwargs аргументи ключових слів. Вони дозволили
ви пишете обгортки, які працюють з будь-якою функцією.
functools.wraps: зберегти метадані
Без wraps декорована функція втрачає __name__, __doc__, підказки:
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
...
return func(*args, **kwargs)
return wrapperПриклад: @timeit
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
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) отримує фактичні аргументи, що передаються декорованій функції під час викликів.
Спробуйте самі
Напишіть декоратор `double_result`, який викликає декоровану функцію та повертає її результат * 2. Застосуйте його до функції `f(x)`, яка повертає x + 1. Обчисліть `f(10)`.
Показати підказку
Обгортка викликає функцію func і повертає результат, помножений на 2.
Рішення доступне після 3 спроб
Повторна вправа
Використовуйте functools.lru_cache для прискорення рекурсивної функції fib(n). Обчислити fib(30) і призначити `r`. Оцініть `r`.
Показати підказку
@lru_cache(maxsize=None) над def.
Рішення доступне після 3 спроб
Додатковий виклик
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 спроб