Przejdź do głównej treści
eLearner.app
Moduł 9 · Lekcja 4 z 436/36 w kursie~14 min
Lekcje modułu (4/4)

Dekoratory: funkcje modyfikujące funkcje

A dekorator (decorator) to funkcja, która przyjmuje inną funkcję i zwraca jej "udekorowaną" wersję. Jest to potężny sposób na dodawanie zachowań (logowanie, pomiar czasu, buforowanie/caching, ponowne próby, walidacja) bez modyfikowania wnętrza oryginalnej funkcji.

Funkcje jako wartości

W Pythonie funkcje są obiektami: możesz je przekazywać, zwracać oraz przypisywać.

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

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

Minimalny dekorator

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

Składnia @decorator

Przypisanie somma = log(somma) można zapisać bardziej elegancko za pomocą @:

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

To dokładnie to samo. Zapis @log nad definicją jest równoważny z: „jak tylko ta funkcja zostanie zdefiniowana, zastąp ją przez log(funkcja)”.

*args, **kwargs: akceptowanie dowolnej sygnatury

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

*args gromadzi argumenty pozycyjne, a **kwargs argumenty nazwane. Pozwalają one pisać wrappery działające z dowolną funkcją.

functools.wraps: zachowywanie metadanych

Bez użycia wraps udekorowana funkcja traci atrybuty __name__, __doc__ oraz podpowiedzi typów:

Python
from functools import wraps

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

Przykład: @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()

Dekoratory z biblioteki standardowej (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 — zapamiętuje (cache-uje) wyniki czystej funkcji.
  • @property — udostępnia metodę jako atrybut (widzieliśmy to w module OOP).
  • @staticmethod, @classmethod — zmieniają sposób przekazywania parametru self.

Dekoratory z argumentami

Dekoratory mogą również przyjmować argumenty. Aby to zaimplementować, musisz zapisać trzy poziomy zagnieżdżonych funkcji: zewnętrzna funkcja przyjmuje argumenty dekoratora, środkowa przyjmuje dekorowaną funkcję, a wewnętrzna (wrapper) przyjmuje rzeczywiste parametry przekazywane do dekorowanej funkcji podczas wywołania.

Spróbuj sam

Ćwiczenie#python.m9.l4.e1
Próby: 0Ładowanie...

Napisz dekorator `double_result`, który wywołuje dekorowaną funkcję i zwraca jej wynik pomnożony przez 2. Zastosuj go do funkcji `f(x)` zwracającej x + 1. Oceń `f(10)`.

Ładowanie edytora...
Pokaż wskazówkę

Wrapper wywołuje func i zwraca jej wynik pomnożony przez 2.

Rozwiązanie dostępne po 3 próbach

Ćwiczenie powtórzeniowe

Ćwiczenie#python.m9.l4.e2
Próby: 0Ładowanie...

Użyj functools.lru_cache do przyspieszenia rekurencyjnej funkcji fib(n). Oblicz fib(30) i przypisz wynik do `r`. Oceń `r`.

Ładowanie edytora...
Pokaż wskazówkę

@lru_cache(maxsize=None) nad instrukcją def.

Rozwiązanie dostępne po 3 próbach

Dodatkowe wyzwanie

Ćwiczenie#python.m9.l4.e3
Próby: 0Ładowanie...

Zdefiniuj dekorator `double_result` który podwaja (mnoży przez 2) wartość zwracaną przez dekorowaną funkcję. Zastosuj dekorator do funkcji `def sum_nums(a, b): return a + b`. Na koniec oceń `sum_nums(3, 4)`.

Ładowanie edytora...
Pokaż wskazówkę

Wrapper wywołuje func(*args, **kwargs) i mnoży wynik przez 2.

Rozwiązanie dostępne po 3 próbach