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!

Decoradores? Really?

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 uso de decorators em Python

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.

Um pouco de prática

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_decorador
def 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 😛

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 inicial
print rotas[1][1]()  # Pagina de contato
print rotas          # [('/index/', < function home_view at 0xb736580c >),
                     #  ('/contato/', < function contato_view at 0xb7365b8c >)]

Referências

Até a próxima…

Fonte: Klaus Laube

Klaus Peter Laube

Mais artigos deste autor »

Tecnólogo em Análise e Desenvolvimento de Sistemas pelo Centro Universitário de Jaraguá do Sul (UNERJ). Desenvolvedor Web de longa data, apaixonado por Python e defensor dos padrões Web. Escreve quando pode no http://www.klauslaube.com.br.


Deixe seu comentário

Seu endereço de e-mail não será publicado. Campos com * são obrigatórios!