2.23. Decoradores

Um decorador é uma peça de sintaxe que envolve uma função (ou um método) noutra função. O envólucro pode adicionar comportamento antes e depois da chamada original, substituir o valor de retorno ou anexar metadados. A forma é:

@wrapper
def f():
    ...

A linha @wrapper é equivalente a f = wrapper(f) – a função f é construída normalmente, depois passada ao wrapper, e o resultado é re-associado ao nome f.

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

Um decorador recebe uma função e devolve uma nova função.

2.23.1. Decoradores de métodos incorporados

Alguns decoradores são fornecidos com o Python e são usados dentro de corpos de classes.

2.23.1.1. @property

Transforma um método num atributo calculado. O invocador acede-lhe como se fosse um atributo simples (sem parênteses), mas um método é executado em cada leitura:

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

Use-o quando um atributo que parece simples precisa de um pequeno cálculo por trás. Se o trabalho for dispendioso, prefira um método regular – os invocadores não esperam que leituras de atributos sejam lentas.

2.23.1.2. @classmethod

Define um método que recebe a classe como primeiro argumento em vez de uma instância. O primeiro parâmetro é convencionalmente chamado 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()

Os métodos de classe são a forma padrão de fornecer construtores alternativos – funções de fábrica que devolvem uma instância construída de forma não padrão.

2.23.1.3. @staticmethod

Define um método que não recebe nem a instância nem a classe – é apenas uma função simples que vive no espaço de nomes da classe por razões de organização:

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

Temperature.c_to_f(100)   # 212.0

Use-o com parcimónia; se uma função realmente não tem nada a ver com o estado da classe, uma função simples ao nível do módulo é geralmente mais limpa.

2.23.2. Escrever um decorador personalizado

Um decorador é uma função que recebe uma função e devolve uma função. A forma mínima:

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)

Resultado:

calling add

O wrapper fecha sobre func e encaminha tudo para ele. *args / **kwargs permitem-lhe funcionar com qualquer função, não apenas com funções de dois argumentos. Este padrão é a base de decoradores mais elaborados (temporização, cache, retentar em caso de falha), mas o núcleo é sempre o mesmo: recebe uma função, devolve uma função.