Direct naar de hoofdinhoud
Module 9 · Les 4 van 436/36 in de cursus~14 min
Modulelessen (4/4)

Decorateurs: functies die functies wijzigen

Een decorateur is een functie die een andere functie aanneemt en a retourneert "versierde" versie ervan. Het is een krachtige manier om gedrag toe te voegen (loggen, timing, caching, opnieuw proberen, validatie) zonder de hoofdtekst van het bestand aan te raken de oorspronkelijke functie.

Functioneert als waarden

In Python zijn functies objecten: je kunt ze doorgeven, retourneren en toewijzen.

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

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

Een minimale decorateur

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 syntaxis

De opdracht somma = log(somma) is eleganter geschreven met @:

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

Het is precies hetzelfde. @log boven de definitie is gelijk aan: "Zodra deze functie is gedefinieerd, vervangt u deze door log(function)".

*args, **kwargs: accepteer elke handtekening

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

*args verzamelt positionele argumenten, **kwargs trefwoordargumenten. Ze lieten je schrijft wrappers die met elke functie werken.

functools.wraps: bewaar metadata

Zonder wraps verliest de gedecoreerde functie __name__, __doc__, hints:

Python
from functools import wraps

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

Voorbeeld: @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()

Decorateurs uit de 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 — onthoudt de resultaten van een zuivere functie.
  • @property — toont een methode als attribuut (te zien in de OOP-module).
  • @staticmethod, @classmethod — wijzig hoe self wordt doorgegeven.

Decorateurs met argumenten

Decorateurs kunnen ook argumenten accepteren. Om dit te implementeren moet je drie niveaus van geneste functies schrijven: de buitenste functie ontvangt de argumenten van de decorateur, de middelste functie ontvangt de versierde functie en de binnenste functie (wrapper) ontvangt de feitelijke argumenten die tijdens aanroepen aan de versierde functie worden doorgegeven.

Probeer het zelf

Oefening#python.m9.l4.e1
Pogingen: 0Laden…

Schrijf een decorateur `double_result` die de gedecoreerde functie aanroept en het resultaat * retourneert. 2. Pas het toe op een functie `f(x)` die x + 1 retourneert. Evalueer `f(10)`.

Editor laden…
Toon hint

De wrapper roept func aan en retourneert het resultaat vermenigvuldigd met 2.

Oplossing beschikbaar na 3 pogingen

Oefening beoordelen

Oefening#python.m9.l4.e2
Pogingen: 0Laden…

Gebruik functools.lru_cache om een ​​recursieve fib(n)-functie te versnellen. Bereken fib(30) en wijs deze toe aan `r`. Evalueer `r`.

Editor laden…
Toon hint

@lru_cache(maxsize=None) boven de def.

Oplossing beschikbaar na 3 pogingen

Extra uitdaging

Oefening#python.m9.l4.e3
Pogingen: 0Laden…

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)`.

Editor laden…
Toon hint

De wrapper roept func(*args, **kwargs) aan en vermenigvuldigt het resultaat met 2.

Oplossing beschikbaar na 3 pogingen