2.23. Decoradores

Um decorador é uma construção de sintaxe que envolve uma função (ou um método) em outra função. O invólucro pode adicionar comportamento antes e depois da chamada original, substituir o valor de retorno ou anexar metadados. O formato é:

@wrapper
def f():
    ...

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

Uma função entrando em uma caixa "wrapper" à esquerda e saindo à direita como uma função decorada.

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

2.23.1. Decoradores de método embutidos

Alguns decoradores vêm com o Python e são usados dentro de corpos de classe.

2.23.1.1. @property

Transforma um método em um atributo computado. O chamador o acessa como se fosse um atributo comum (sem parênteses), mas um método é executado a 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 caro, prefira um método comum – os chamadores 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 de 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()

Métodos de classe são a forma padrão de fornecer construtores alternativos – funções de fábrica que retornam uma instância construída de uma maneira 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 comum que vive no namespace da classe por motivos 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 moderação; se uma função realmente não tem nada a ver com o estado da classe, uma função comum em nível de módulo costuma ser mais limpa.

2.23.2. Escrevendo um decorador personalizado

Um decorador é uma função que recebe uma função e retorna uma função. O formato mínimo:

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)

Saída:

calling add

O wrapper faz closure sobre func e encaminha tudo para ele. *args / **kwargs permite que ele funcione com qualquer função, não apenas com aquelas de dois argumentos. Esse padrão é a base de decoradores mais elaborados (medição de tempo, cache, nova tentativa em caso de falha), mas o núcleo é sempre o mesmo: receber uma função, retornar uma função.