2.23. Decoratori

Un decorator este o sintaxă care învelește o funcție (sau o metodă) într-o altă funcție. Învelitoarea poate adăuga comportament înainte și după apelul original, poate înlocui valoarea returnată sau poate atașa metadate. Forma este:

@wrapper
def f():
    ...

Linia @wrapper este echivalentă cu f = wrapper(f) – funcția f este construită normal, apoi predată lui wrapper, iar rezultatul este relegat de numele f.

O funcție care intră într-o cutie „wrapper” în stânga și iese în dreapta ca funcție decorată.

Un decorator primește o funcție și returnează o funcție nouă.

2.23.1. Decoratori de metode încorporați

Câțiva decoratori sunt livrați cu Python și sunt folosiți în interiorul corpurilor de clasă.

2.23.1.1. @property

Transformă o metodă într-un atribut calculat. Apelantul îl accesează ca și cum ar fi un atribut obișnuit (fără paranteze), dar o metodă rulează la fiecare citire:

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

Folosește-l atunci când un atribut care pare simplu necesită un mic calcul în spate. Dacă operația este costisitoare, preferă o metodă obișnuită – apelanții nu se așteaptă ca citirile de atribute să fie lente.

2.23.1.2. @classmethod

Definește o metodă care primește clasa drept prim argument în loc de o instanță. Primul parametru este numit prin convenție 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()

Metodele de clasă reprezintă modalitatea standard de a furniza constructori alternativi – funcții fabrică ce returnează o instanță construită într-un mod diferit de cel implicit.

2.23.1.3. @staticmethod

Definește o metodă care nu primește nici instanța, nici clasa – este doar o funcție obișnuită care trăiește în spațiul de nume al clasei din motive de organizare:

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

Temperature.c_to_f(100)   # 212.0

Folosește-l cu moderație; dacă o funcție chiar nu are nicio legătură cu starea clasei, o funcție obișnuită la nivel de modul este de obicei mai curată.

2.23.2. Scrierea unui decorator personalizat

Un decorator este o funcție care primește o funcție și returnează o funcție. Forma minimă:

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)

Ieșire:

calling add

Funcția wrapper se închide peste func și îi transmite totul. *args / **kwargs îi permit să funcționeze cu orice funcție, nu doar cu cele cu două argumente. Acest tipar este fundamentul unor decoratori mai elaborați (cronometrare, memorare în cache, reîncercare la eșec), dar esența este întotdeauna aceeași: primește o funcție, returnează o funcție.