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.

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

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.