2.23. Dekorátorok

A dekorátor egy olyan szintaxiselem, amely egy függvényt (vagy egy metódust) becsomagol egy másik függvénybe. A becsomagoló viselkedést adhat hozzá az eredeti hívás előtt és után, lecserélheti a visszatérési értéket, vagy metaadatot csatolhat hozzá. Az alakja:

@wrapper
def f():
    ...

A @wrapper sor egyenértékű az f = wrapper(f) kifejezéssel – az f függvény normálisan épül fel, majd átadódik a wrapper függvénynek, az eredmény pedig újraköttetik az f névhez.

Egy függvény balról belép egy "becsomagoló" dobozba, és jobbról dekorált függvényként lép ki.

Egy dekorátor bevesz egy függvényt, és visszaad egy új függvényt.

2.23.1. Beépített metódusdekorátorok

Néhány dekorátor a Pythonnal együtt érkezik, és osztálytörzseken belül használatos.

2.23.1.1. @property

Egy metódust számított attribútummá alakít. A hívó úgy fér hozzá, mintha egyszerű attribútum lenne (zárójelek nélkül), de minden olvasáskor lefut egy metódus:

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

Akkor használd, amikor egy egyszerűnek tűnő attribútum mögött egy apró számításra van szükség. Ha a munka költséges, inkább egy szokásos metódust válassz – a hívók nem számítanak arra, hogy az attribútumolvasás lassú legyen.

2.23.1.2. @classmethod

Olyan metódust definiál, amely első argumentumként egy példány helyett magát az osztályt kapja meg. Az első paramétert szokás szerint cls névvel látják el:

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

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

p = Point.origin()

Az osztálymetódusok a szabványos módja az alternatív konstruktorok biztosításának – olyan gyártófüggvények, amelyek egy nem alapértelmezett módon felépített példányt adnak vissza.

2.23.1.3. @staticmethod

Olyan metódust definiál, amely sem a példányt, sem az osztályt nem kapja meg – ez csupán egy egyszerű függvény, amely szervezési okokból él az osztály névterében:

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

Temperature.c_to_f(100)   # 212.0

Takarékosan használd; ha egy függvénynek valójában semmi köze az osztály állapotához, általában tisztább egy egyszerű modulszintű függvény.

2.23.2. Egyedi dekorátor megírása

A dekorátor egy olyan függvény, amely bevesz egy függvényt, és visszaad egy függvényt. A minimális alakja:

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)

Kimenet:

calling add

A wrapper lezárja a func függvényt, és mindent továbbít hozzá. A *args / **kwargs lehetővé teszi, hogy bármely függvényen működjön, ne csak kétargumentumosakon. Ez a minta az alapja a kidolgozottabb dekorátoroknak (időmérés, gyorsítótárazás, hiba esetén újrapróbálkozás), de a lényeg mindig ugyanaz: vegyél be egy függvényt, adj vissza egy függvényt.