@decorators
por Walter Cruz on 17/04/2007
in Python, Programação, Linguagens
Python 2.4 trouxe uma novidade interessante: os decorators. Vamos dar uma olhada no documento que traz as novidades do python 2.4 para entender isso melhor:
Python 2.2 estendeu o modelo de objetos do Python adicionando métodos estáticos e métodos de classe, mas não estendeu a sintaxe do Python com uma nova forma de definir métodos estáticos ou de classe. Ao invés disso, você usa a declaração def da forma normal, e passa o método resultante para a função staticmethod() ou classmethod() function que encapsula a função como um método do tipo correspondente. Seu código ficaria semelhante a isso:
class C: def meth (cls): ... meth = classmethod(meth) # Reassocia o nome ao método de classeSe o método fosse muito longo, seria fácil de esquecer a invocação de classmethod() depois do corpo da função.
(...)
A notação é copiada de Java e usa o caracter "@" como um indicador. Usando a nova sintaxe, o exemplo acima poderia ser escrito como:
class C: @classmethod def meth (cls): ...
Ou seja: o decorator é usado para encapsular uma função em outra função. Poderíamos dizer que esse é um Design Pattern do Python.É parecido com o decorator descrito no livro da Gangue dos Quatro, mas não exatamente igual: lá, se fala de uma classe que decora outra classe. Aqui, estamos falando de uma função que decora outra função. Decoradores de classe foram propostos para o python 3000, mas a discussão não parece ter andado muito pra frente.
O que python 2.4 fez foi pegar uma forma comum de desenvolvimento no python (encapsular uma função em outra função) e adicionar um açucar sintático. Um exemplo:
Podemos usar o módulo atexit para registrar uma função quando o interpretador python for encerrado corretamente.
>>> import atexit
>>> @atexit.register
... def sai():
... print('Adeus')
...
>>>
Adeus
Sem o decorator, o código teria de ser digitado da seguinte forma:
>>> import atexit
>>> def sai():
... print('Adeus')
...
>>> atexit.register(sai)
>>>
Adeus
Veja que sem o decorator, o registro da função para o atexit ocorreu depois da declaração da função. Com o decorator, ela é registrada logo no início da declaração da função, tornando muito mais claro ao programador o registro da função no atexit.
Decorador para garantir os tipos dos parâmetros passados
from types import *
class param:
def __init__(self, *args):
self.__types = args
self.__argc = len(args)
def __call__(self, f):
def func(*args):
assert len(args) == self.__argc
for i in xrange(self.__argc):
assert isinstance(args[i], self.__types[i])
return f(*args)
func.__name__ = f.__name__
func.__doc__ = f.__doc__
return func
Digamos que uma função recebe o primeiro argumento inteiro e o segundo string:
@param(IntType, StringType)
def funcao(x, s):
....
Precisamos de decorators?
Avaliando mais profundamente, os @decorators são uma pequena modificação que não adicionam nada novo à linguagem. Qual a vantagem disso?
Segundo Michele Simionato, "a introdução dos decorators mudou significativamente a forma que estruturamos nossos programas em Python. Creio que essas mudanças são para melhor, e que decorators são uma grande idéia já que::
- decorators ajudam a reduzir boilerplate code
- decorators ajudam na separação de responsabilidades
- decorators melhoram a legibilidade e a manutebilidade
- decorators são muito explícitos.
Uma coisa a ser notada: um decorador muda a assinatura da função que ele decora. Um exemplo prático:
def decorator_trace(f):
def newf(*args, **kw):
'''documentacao de decorator'''
print "calling %s with args %s, %s" % (f.__name__, args, kw)
return newf
@decorator_trace
def funcao(argumento):
'''documentacao de funcao'''
print(argumento)
funcao('a')
print(funcao.__name__)
print(funcao.__doc__)
Resultado:
calling funcao with args ('a',), {}
newf
documentacao de decorator
Observe que a função funcao teve seu nome e documentação trocadas pelas do decorator. Para que isso não ocorra, seria necessário que decorator_trace fosse declarada da seguinte forma:
def decorator_trace(f):
def newf(*args, **kw):
print "calling %s with args %s, %s" % (f.__name__, args, kw)
return f(*args, **kw)
newf.__name__ = f.__name__
newf.__doc__ = f.__doc__
return newf
Python 2.5 provê uma forma mais simples de decorar a função sem mudar sua assinatura: usando o update_wrapper do módulo functools.
Um exemplo de decorator que não muda a assinatura da função decorada:
from functools import update_wrapper
def decorator_trace(f):
def newf(*args, **kw):
print "calling %s with args %s, %s" % (f.__name__, args, kw)
return f(*args, **kw)
return update_wrapper(newf, f)
Exemplos de uso
- Jeremy Jones publicou no site da O'Reilly um tutorial de como usar decorators para gerenciar threads no pyGTK
- O Turbo Gears usa decorators para decidir quais métodos devem ser expostos na web
Nota
Na verdade, o texto contém uma pequena inverdade, que você já deve ter percebido à essa altura: qualquer objeto callable em python pode ser usado como decorator(no decorator pra verificação de tipos, ele é uma classe e não uma função).
Em python existem 4 objetos callable (funções, métodos, classes e algumas instâncias de classes cuja classe tenha o método __call__ (http://www.python.org/doc/1.4/tut/node92.html)). Por exemplo, no artigo em http://www.ddj.com/184406073#l10 na listagem 5, Phillip Eby usa uma classe como decorator. Nesse caso, hello passa a ser uma instância da classe traced.
class traced:
def __init__(self,func):
self.func = func
def __call__(__self,*__args,**__kw):
print "entering", __self.func
try:
return __self.func(*__args,**__kw)
finally:
print "exiting", __self.func
@traced
def hello():
print "Hello, world!"
hello()
print(hello.__class__)
print(dir(hello))
Referências
- http://safari.oreilly.com/0132269937/ch14lev1sec1
- http://www.ddj.com/184406073
- Rodrigo Cacilhas, que me passou o código do decorator param
Subscribe: 



Muuuuuuuito bom!
Nem preciso falar muito né?!
Há tempos que eu queria ler algo tão bem explicado sobre decorators!
Valeu!
Walter, muito bom! Melhor do que os outros artigos que li sobre decorators.
Hehehe! Valeu semente!
Estou com um artigo de metaclasses no forno :)
Parabéns pela qualidade do Blog. Aproveitamos e sugerimos uma visita para www.orkut.etc.br.