« Google Summer of Code 2007Rails, dispatch.fcgi e quebras de linha »

@decorators

17/04/2007

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 classe

Se 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


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


Email por Walter Cruz em Python, Programação, Linguagens
Tags: decorators, metaprogramacao, python

Endereço de trackback para este post

Trackback URL (clique direito e copie atalho/localização do link)

4 comentários

Comentário de: Gabriel [Visitante] Email · http://www.nacaolivre.org
Muuuuuuuito bom!
Nem preciso falar muito né?!
Há tempos que eu queria ler algo tão bem explicado sobre decorators!

Valeu!
18/04/2007 @ 00:14
Comentário de: semente [Visitante] · http://semente.taurinus.org
Walter, muito bom! Melhor do que os outros artigos que li sobre decorators.
18/04/2007 @ 11:00
Comentário de: Walter Cruz [Membro] Email · http://waltercruz.com
Hehehe! Valeu semente!

Estou com um artigo de metaclasses no forno :)
20/04/2007 @ 09:43
Comentário de: Orkut.etc.br [Visitante] Email · http://www.orkut.etc.br
Parabéns pela qualidade do Blog. Aproveitamos e sugerimos uma visita para www.orkut.etc.br.
26/04/2007 @ 12:38

Deixe seu comentário


Seu endereço de e-mail não será revelado nesse site.

Sua URL será exibida.
PobreExcelente
(Quebras de linha se tornam <br />)
(Nome, e-mail & website)
(Permitir que usuários o contatem através de um formulário eletrônico (seu e-mail não será exibido.))

You can just use your OpenID to provide your name, e-mail and url.