| « Google Summer of Code 2007 | Rails, dispatch.fcgi e quebras de linha » |
@decorators
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
Endereço de trackback para este post
Trackback URL (clique direito e copie atalho/localização do link)
4 comentários
Nem preciso falar muito né?!
Há tempos que eu queria ler algo tão bem explicado sobre decorators!
Valeu!
Estou com um artigo de metaclasses no forno :)
