2.23. Decorators

Een decorator is een stukje syntaxis dat een functie (of een methode) in een andere functie wikkelt. De wrapper kan gedrag toevoegen voor en na de oorspronkelijke aanroep, de retourwaarde vervangen, of metadata aanhechten. De vorm is:

@wrapper
def f():
    ...

De regel @wrapper is equivalent aan f = wrapper(f) – de functie f wordt normaal gebouwd, vervolgens aan wrapper overhandigd, en het resultaat wordt opnieuw aan de naam f gebonden.

Een functie die links een "wrapper"-box binnengaat en rechts naar voren komt als een gedecoreerde functie.

Een decorator neemt een functie in en geeft een nieuwe functie terug.

2.23.1. Ingebouwde methode-decorators

Een paar decorators worden met Python meegeleverd en worden binnen klasse-bodies gebruikt.

2.23.1.1. @property

Verandert een methode in een berekend attribuut. De aanroeper benadert het alsof het een gewoon attribuut is (geen haakjes), maar bij elke lezing draait er een methode:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

Gebruik het wanneer een attribuut dat er eenvoudig uitziet, een kleine berekening erachter nodig heeft. Als het werk duur is, verkies dan een gewone methode – aanroepers verwachten niet dat het lezen van attributen traag is.

2.23.1.2. @classmethod

Definieert een methode die de klasse als eerste argument ontvangt in plaats van een instance. De eerste parameter heet conventioneel cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Klassemethoden zijn de standaardmanier om alternatieve constructors te bieden – fabrieksfuncties die een instance teruggeven die op een niet-standaard manier is gebouwd.

2.23.1.3. @staticmethod

Definieert een methode die noch de instance noch de klasse ontvangt – het is gewoon een gewone functie die om organisatorische redenen in de naamruimte van de klasse leeft:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

Gebruik het spaarzaam; als een functie echt niets met de toestand van de klasse te maken heeft, is een gewone functie op moduleniveau meestal schoner.

2.23.2. Een aangepaste decorator schrijven

Een decorator is een functie die een functie neemt en een functie teruggeeft. De minimale vorm:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

Uitvoer:

calling add

De wrapper sluit zich rond func en stuurt alles ernaartoe door. *args / **kwargs laat hem op elke functie werken, niet alleen op functies met twee argumenten. Dit patroon is de basis van uitgebreidere decorators (timing, caching, retry-on-failure) maar de kern is altijd hetzelfde: neem een functie in, geef een functie terug.