Hoje me deparei com um excelente texto sobre decorators que me inspirou a escrever algo sobre o assunto que para muita gente ainda é um tanto quanto nebuloso. Vou tentar aqui explicar o funcionamento de um decorator e mostrar algumas possíveis aplicações.
Aviso aos iniciantes: esse assunto pode ser um pouco confuso para quem ainda está iniciando em programação. Caso sinta dificuldades, não desanime e pule antes para a seção que contém as referências para melhor entendimento do texto.
O que é um decorator?
Um decorator é uma forma prática e reusável de adicionarmos funcionalidades às nossas funções/métodos/classes, sem precisarmos alterar o código delas.
O framework para desenvolvimento web Django oferece diversos decorators prontos para os desenvolvedores. Por exemplo, para exigir que o acesso a determinada view seja feito somente por usuários autenticados, basta preceder o código da view (que em geral é uma funçãozinha ou classe) pelo decorator @login_required. Exemplo:
@login_required
def boas_vindas(request):
return HttpResponse("Seja bem-vindo!")
É claro que isso não é mágica. Como a gente pode ver no código-fonte do decorator login_required, os detalhes estão apenas sendo ocultados do código-fonte do nosso projeto. Assim, ao invés de ter que, a cada view, escrever o código que verifica se determinado usuário está autenticado, basta usar o decorator. Isso faz com que adicionemos a funcionalidade de verificar se um usuário está ou não logado no site, com uma linha de código apenas. Que barbada, não?
O decorator é um açúcar sintático que Python oferece aos desenvolvedores desde a versão 2.4, facilitando o desenvolvimento de códigos reusáveis.
OK, mas como implementar um decorator?
Você já sabe como um decorator pode ser usado, então agora vamos entender as internas desse recurso do Python.
Um decorator é implementado como uma função que recebe uma função como parâmetro, faz algo, então executa a função-parâmetro e retorna o resultado desta. O algo é a funcionalidade que adicionamos a nossa função original através do decorator.
Vamos escrever um decorator que sirva para escrever na tela o nome da função a ser executada, antes da execução da mesma. Como descrito acima, precisamos definir uma função que receba outra função como parâmetro, imprima o nome dessa, execute a função e retorne o seu resultado. Veja o código:
def echo_funcname(func):
def finterna(*args, **kwargs):
print "Chamando funcao: %s()" % (func.__name__)
return func(*args, **kwargs)
return finterna
@echo_funcname
def dobro(x):
return x*2
dobro(10)
Antes de mais nada, observe atentamente a função echo_funcname, pois existem alguns conceitos importantes dentro dela.
def echo_funcname(func):
def finterna(*args, **kwargs):
print "Chamando funcao: %s()" % (func.__name__)
return func(*args, **kwargs)
return finterna
Veja que ela receba um parâmetro func (que espera-se que seja uma função) e retorna outra função (finterna). A função retornada, finterna, é “configurada” para executar ao seu final a função recebida como argumento pela função externa (echo_funcname), bem como retornar o valor de retorno da função recebida. Em outras palavras, echo_funcname() cria dentro de si próprio uma função chamada finterna(), que no final (linha 5) chama a função recebida como parâmetro. Mas, é importante perceber que a palavra-chave def somente cria a função (isto é, instancia um objeto do tipo função), não executando ela. Ou seja, echo_funcname cria uma função, configura ela para executar func() ao seu final, não a executa, mas sim somente retorna o objeto função, que então poderá ser chamada por quem recebê-la. (um assunto muito importante para o entendimento desse conceito de função dentro de função é o conceito de closures).
Caso tenha ficado confuso, perceba que finterna é um objeto como qualquer outro que estamos acostumados a criar dentro de nossas funções, como uma lista, por exemplo. A diferença é que esse objeto é uma função, o que pode parecer um pouco estranho, em um primeiro momento. Sendo um objeto qualquer, a função é instanciada, recebe um nome (finterna), e pode ser retornada, assim como todo objeto (tudo isso sem ser executada, pois não chamamos finterna).
Veja um exemplo de visualização de uma função que define outra função internamente (visualização gerada pelo excepcional pythontutor.com):
Se quiser visualizar a versão interativa, clique aqui (powered by PythonTutor.com).
Tendo a função echo_funcname() definida, agora poderíamos fazer o seguinte:
def echo_funcname(func):
def finterna(*args, **kwargs):
print "Chamando funcao: %s()" % (func.__name__)
return func(*args, **kwargs)
return finterna
def dobro(x):
""" Uma funcao exemplo qualquer.
"""
return 2*x
dobro_com_print = echo_funcname(dobro)
print dobro_com_print(10)
Ao executar o código acima, teremos como resposta na tela:
Chamando funcao: dobro() 20
Criamos uma função chamada dobro(), que recebe um número e retorna o dobro desse número. Depois, passamos esse objeto do tipo function para a função echo_funcname() e recebemos como retorno outro objeto do tipo function, ao qual referenciamos como dobro_com_print. Perceba que dobro_com_print nada mais é do que uma referência a uma função mais ou menos assim:
def finterna(*args, **kwargs):
print "Chamando funcao: %s()" % (dobro.__name__)
return dobro(*args, **kwargs)
Essa função foi gerada dentro de echo_funcname() e retornada, já com dobro no lugar de func. Assim, quando chamamos a função como em print dobro_com_print(10), estamos chamando a função acima, e passando 10 como argumento.
Mas, esse negócio todo de passar uma função como parâmetro e receber uma função como retorno de uma chamada de função é um pouco confuso. Para abstrair um pouco esses detalhes, Python oferece a sintaxe do @nome_do_decorator que precede a definição de funções. Assim, ao invés de:
dobro_com_print = echo_funcname(dobro) print dobro_com_print(10)
Poderíamos apenas preceder a definição da função dobro() com o decorator @echo_funcname:
@echo_funcname
def dobro(x):
""" Uma funcao exemplo qualquer.
"""
return 2*x
Agora, ao chamar a função dobro(), estaríamos chamando a função decorada (isto é, acrescida de funcionalidades). No nosso caso, o decorator apenas adiciona a impressão na tela de um aviso sobre a chamada da função.
Enfim, um decorator nada mais é do que uma função que recebe outra função como parâmetro, gera uma nova função que adiciona algumas funcionalidades à função original e a retorna essa nova função.
Concluindo …
Os decorators formam um recurso muito importante para diminuir a duplicação e aumentar o reuso de código em um projeto. O conceito pode ser um pouquinho complicado para entender de primeira, mas uma vez que você o domine, você começará a perceber diversas oportunidades para implementar e usar decorators em seus projetos.
Leia mais
Por se tratar de um assunto mais complicado para iniciantes, segue aqui uma lista de textos que poderiam ser lidos, possibilitando um melhor entendimento sobre o assunto.
Funções como objetos:
- O segredo dos objetos-função – Aprenda a Programar, por Luciano Ramalho
- What are “first class” objects? – StackOverflow
- First-class everything – The History of Python
- First-class function – Wikipedia
Closures:

Você precisa fazer login para comentar.