2.23. Dekoratorer

En dekorator är en bit syntax som lindar in en funktion (eller en metod) i en annan funktion. Inlindaren kan lägga till beteende före och efter det ursprungliga anropet, ersätta returvärdet eller bifoga metadata. Formen är:

@wrapper
def f():
    ...

Raden @wrapper är likvärdig med f = wrapper(f) – funktionen f byggs normalt, lämnas sedan över till wrapper, och resultatet binds om till namnet f.

En funktion som går in i en "wrapper"-låda till vänster och kommer ut till höger som en dekorerad funktion.

En dekorator tar in en funktion och returnerar en ny funktion.

2.23.1. Inbyggda metoddekoratorer

Några få dekoratorer levereras med Python och används inuti klasskroppar.

2.23.1.1. @property

Förvandlar en metod till ett beräknat attribut. Anroparen kommer åt det som om det vore ett vanligt attribut (inga parenteser), men en metod körs vid varje läsning:

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

Använd det när ett attribut som ser enkelt ut behöver en liten beräkning bakom sig. Om arbetet är dyrt, föredra en vanlig metod – anropare förväntar sig inte att attributläsningar ska vara långsamma.

2.23.1.2. @classmethod

Definierar en metod som tar emot klassen som sitt första argument i stället för en instans. Den första parametern namnges enligt konvention 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()

Klassmetoder är standardsättet att tillhandahålla alternativa konstruktorer – fabriksfunktioner som returnerar en instans byggd på ett icke-standardmässigt sätt.

2.23.1.3. @staticmethod

Definierar en metod som varken tar emot instansen eller klassen – den är bara en vanlig funktion som bor i klassens namnrymd av organisatoriska skäl:

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

Temperature.c_to_f(100)   # 212.0

Använd det sparsamt; om en funktion verkligen inte har något med klassens tillstånd att göra är en vanlig funktion på modulnivå oftast renare.

2.23.2. Att skriva en egen dekorator

En dekorator är en funktion som tar en funktion och returnerar en funktion. Minimiformen:

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)

Utmatning:

calling add

wrapper sluter över func och vidarebefordrar allt till den. *args / **kwargs låter den fungera på vilken funktion som helst, inte bara sådana med två argument. Det här mönstret är grunden för mer utarbetade dekoratorer (tidtagning, cachning, försök-igen-vid-fel) men kärnan är alltid densamma: ta in en funktion, returnera en funktion.