@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 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

This entry was posted in Linguagens, Programação, Python and tagged , , . Bookmark the permalink.

6 Responses to @decorators

  1. Muuuuuuuito bom!
    Nem preciso falar muito né?!
    Há tempos que eu queria ler algo tão bem explicado sobre decorators!

    Valeu!

  2. Walter, muito bom! Melhor do que os outros artigos que li sobre decorators.

  3. Hehehe! Valeu semente!

    Estou com um artigo de metaclasses no forno :)

  4. Parabéns pela qualidade do Blog. Aproveitamos e sugerimos uma visita para http://www.orkut.etc.br.

  5. Pingback: Decorators em Python | Profissionais TI - Pra quem respira informação

  6. Pingback: Python Decorators – Blog do Andre Fonseca

Deixe um Comentário

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>