Tuesday, August 16, 2011

Decorators em Python

E na mesma linha dos closures, eu passei a saber realmente o que são decorators depois de utilizar a framework Django. Se você está trabalhando com Orientação a Objetos e até agora não se deparou com este recurso, te garanto que um dia você precisará dele… afinal, é um dos Design Patterns mais bacanas (e úteis) que já vi.

Vamos lá “decorar” nossos métodos Python!

Eu não saberia explicar de uma forma melhor do que foi explicado pela Wikipedia, o que é o padrão decorator:

Intenção: Acrescentar responsabilidades a um objeto dinamicamente. Prover alternativa flexível ao uso de subclasses para se estender a funcionalidade de uma classe;Motivação: Objeto usado possui as funcionalidades básicas, mas é necessário adicionar funcionalidades adicionais a ele que podem ocorrer antes ou depois da funcionalidade básica. Funcionalidades devem ser adicionadas em instancias individuais e não na classe;Consequências: Mais flexibilidade do que herança.

Encontramos na Wiki do Python uma explicação mais objetiva e esclarecedora:

Decorators alteram dinamicamente a funcionalidade de uma função, método ou classe, sem uso direto de subclasses ou alterando o código-fonte da função “decorada”.

O Python começou a dar suporte a decorators a partir da versão 2.4.

Você terá a sua disposição alguns decoradores built-in e também poderá criar os seus próprios sem muito dificuldade. É possível identificar um decorator através do caractere @, por exemplo, a instrução abaixo declara o método say_hello da classe People como estático:

class People: @staticmethod def say_hello(): print 'Hello!'

Vale notar que podemos reproduzir o comportamento acima sem utilizar a sintaxe especial de decorators (mas não deixamos de utilizar o conceito):

class People: def say_hello(): print 'Hello!' say_hello = staticmethod(say_hello)

Quer conhecer mais sobre decorators em Python? Leia a PEP 318 – Decorators for functions and methods.

Vamos por a mão na massa e criar o nosso próprio decorator:

# meu_decorator.py def meu_decorador(alvo): def wrapper(): print 'Chamando a funcao "%s"' % alvo.__name__ return alvo()  return wrapper @meu_decoradordef meu_alvo(): print 'Eu sou um alvo!' meu_alvo()

Chamando o script acima, teremos o seguinte resultado:

$ python meu_decorator.py Chamando a funcao "meu_alvo"Eu sou um alvo!

Vou tentar seguir um fluxo que deixe claro o que o procedimento está realizando.

O comportamento da função meu_alvo é muito simples: imprimir “Eu sou um alvo!” na tela. Mas o @meu_decorador está lá para complicar a nossa vida :P

Com a chamada de @meu_decorador logo acima de meu_alvo, fica claro que na verdade estamos passando meu_alvo como um parâmetro (alvo) para o método meu_decorador, encontrado logo no início do arquivo. Note que o método retorna wrapper sem os parênteses no final (que caracterizam uma chamada de função), ele está retornando apenas a referência ao método wrapper, que será de fato “executado” externamente.

Dentro da função wrapper temos a impressão da string ‘Chamando a funcao “meu_alvo”‘ e a execução de meu_alvo. Isto deve-se ao fato de que alvo nada mais é que uma referência a função meu_alvo, que passamos como argumento para meu_decorador através do @meu_decorador logo acima da função meu_alvo, certo?

Então resumindo isso tudo, o resultado final é que meu_alvo() no final do arquivo na verdade é a execução da referência a wrapper, ou seja, é o mesmo que ler “wrapper()“. Ele fará o print e posteriormente retornará o resultado de meu_alvo, que nada mais é que a impressão de “Eu sou um alvo!”.

Bacana não? Aqui vai mais um para deixar as coisas um pouco mais claras… vamos simular o esquema de roteamento de uma framework Web:

rotas = [] def rota(endereco): def wrapper(fn): rotas.append((endereco, fn))  return wrapper @rota('/index/')def home_view(): return 'Pagina inicial' @rota('/contato/')def contato_view(): return 'Pagina de contato' print rotas[0][1]() # Pagina inicialprint rotas[1][1]() # Pagina de contatoprint rotas # [('/index/', < function home_view at 0xb736580c >), # ('/contato/', < function contato_view at 0xb7365b8c >)]

Até a próxima…

Fonte: Klaus Laube

0 comments:

Post a Comment