Este recurso incrível apareceu na linguagem quase se desculpando, e com preocupações de que poderia não ser tão útil. Eu, por minha vez, acredito que em tempo ele será visto como um dos recursos mais poderosos na linguagem. O problema é que todas as introduções à decorators que eu já vi foram bastante confusas, então vou tentar consertar isso neste artigo.
Decorators vs. decorators pattern
Primeiro, você precisa entender que a palavra “decorator” foi usada com certo receio, porque existia uma preocupação em que confundissem o decorator pattern do livro Design Patterns. Outros termos foram considerados para substituí-lo, mas “decorator” parece ser o escolhido.
Na verdade, você pode utilizar os decorators do Python para implementar um decorator pattern, mas esse é um uso muito limitado dele. Os decorators do Python, acredito, se equivalem mais aos macros.
História dos macros
O macro tem uma longa história, mas a maioria das pessoas provavelmente tiveram experiências com os macro processadores C. Os problemas com os macros C eram os seguintes: eles estavam em uma linguagem diferente da C, e o comportamento deles, às vezes, era bizarro e inconsistente com o comportamento do resto do C.
Tanto o Java, quanto o C# adicionaram anotações, que permitem que você faça algumas coisas para os elementos da linguagem. Ambos têm problemas para fazer o que você quer. Às vezes você tem que pular através de um círculo grande e complexo. Há também o fato de que esse recurso de anotações tem suas mãos atadas pela escravidão e disciplina (ou como Martin Fowler gentilmente coloca: “Direcionamento”) natural dessas linguagens.
Muitos programadores C++ (inclusive eu) notaram as habilidades geradoras de templates C++ e utilizaram esse recurso de uma maneira macro.
Muitas outras linguagens incorporaram os macros. Eu, ainda sem saber muito sobre eles, ouso a dizer que os decoradores do Python são parecidos com os macros do Lips em poder e em possibilidades.
O objetivos dos macros
Acredito que seja certo dizer que o objetivo dos marcos em uma linguagem é fornecer uma maneira de modificar os elementos da mesma. É isso que os decorators fazem no Python: eles modificam funções. É por isso que eles normalmente fornecem uma alternativa mais simples para metaclasses.
As maiores falhas da maioria das abordagens de modificações próprias das linguagens são que elas são muito restritivas e que precisam de uma linguagem diferente (posso dizer que as anotações Java compreendem uma “linguagem diferente”, ainda que com todos os problemas pelos quais você deve passar para produzir uma anotação interessante).
O Python cai na categoria de Fowler de “habilitar” linguagens, então se você quiser fazer modificações, por que criar uma linguagem diferente, ou restrita? Por que simplesmente não usar o Python? E é isso que os decorators de Python fazem.
O que você pode fazer com decorators?
Decorators permitem que você insira, ou modifique o código em funções, ou classes. Parece um pouco como Aspect-Oriented Programming (AOP) em Java, não? Exceto que é muito mais simples e poderoso. Por exemplo, suponha que você gostaria de fazer algo nos pontos de entrada e saída de uma função (como executar algum tipo de segurança, rota, bloqueio, etc – todos os argumentos padrões para AOP). Com decorators, fica assim:
@entryExit
def func1():
print "inside func1()"
@entryExit
def func2():
print "inside func2()"
O @ indica a aplicação do decorator.
Decorators de funções
O decorator de uma função é aplicado a uma definição de função ao ser colocado na linha antes da que começa a definição da função. Por exemplo:
@myDecorator
def aFunction():
print "inside aFunction"
Quando o compilador passa por este código, a aFunction() é compilada e o objeto da função resultante é passado para o código myDecorator, que faz algo para produzir um objeto do tipo função, que é, então, substituído pelo original aFunction().
Com o que o código myDecorator se parece? Bom, a maior parte dos exemplos introdutórios o mostra como uma função, mas eu achei mais fácil começar a compreender os decorators usando classes como mecanismos de decoração, ao invés de funções. Além disso, é mais poderoso.
O único lado ruim de um objeto retornado por um decorator é que ele pode ser usado como uma função – o que basicamente significa que ele deve ser chamável. Assim, quaisquer classes que usarmos como descoradores devem implementar __call__.
O que um decorator faz? Bem, ele pode fazer qualquer coisa, mas normalmente você espera que o código da função original seja usado em algum ponto. Isso não é necessário, no entanto:
class myDecorator(object):
def __init__(self, f):
print "inside myDecorator.__init__()"
f() # Prove that function definition has completed
def __call__(self):
print "inside myDecorator.__call__()"
@myDecorator
def aFunction():
print "inside aFunction()"
print "Finished decorating aFunction()"
aFunction()
Quando você rodar esse código, o verá assim:
inside myDecorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside myDecorator.__call__()
Note que o construtor para myDecorator é executado no ponto da decoração da função. Como podemos chamar f() dento de __init__(), isso mostra que a criação de f() está completa antes do decorator ser chamado. Note, também, que o construtor do decorator recebe o objeto da função sendo decorado. Normalmente, você irá capturar o objeto da função no construtor e depois utilizá-lo no método __call__() (o fato de que decoração e chamado são duas fases claras quando se utiliza classes, é o motivo pelo qual eu argumento que esse é o modo mais fácil e mais poderoso).
Quando aFunction() é chamada depois de ter sido decorada, temos um comportamento completamente diferente; o método myDecorator.__call__() é chamado, ao invés do código original. Isso acontece porque o ato da decoração substitui o objeto da função original com o resutlado da decoração – no nosso caso, o objeto myDecorator substitui aFunction. De fato, antes dos decorators serem adicionados, você tinha que fazer algo muito menos elegante para alcançar a mesma coisa:
def foo(): pass
foo = staticmethod(foo)
Com a adição do operador de decoração @, agora você tem o mesmo resultado dizendo:
@staticmethod
def foo(): pass
Esse é o motivo pelo qual as pessoas argumentam contra os decorators, porque o @ é apenas uma sintaxe simplificada, que significa “passar um objeto de função, através de outra função, e atribuir o resultado à função original”.
Acredito que a razão pela qual os decorators sentem tanto o impacto é porque essa maneira mais fácil, também conhecida como “syntactic sugar“, muda o modo como você pensa programação. Certamente, ele traz a ideia de “aplicar código a outro código”(por exemplo, macros) no pensamento dominante ao formalizá-lo como um construtor de linguagem.
Levemente mais útil
Agora, vamos voltar e implementar o primeiro exemplo. Aqui, vamos fazer a coisa mais comum e de fato utilizar o código nas funções decoradoras:
class entryExit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print "Entering", self.f.__name__
self.f()
print "Exited", self.f.__name__
@entryExit
def func1():
print "inside func1()"
@entryExit
def func2():
print "inside func2()"
func1()
func2()
A saída é:
Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
Você pode ver que as funções decoradoras agora tem as declarações das linha “Entering” e “Exited” em volta do chamado.
O construtor armazena o argumento, que é o objeto da função. No chamado, usamos o atributo da função __name__ para exibir o nome da função, e então chamá-la.
Usando funções como decorators
A única restrição no resultado de um decorator é que ele é chamável, então ele pode substituir propriamente a função decoradora. Nos exemplos acima, eu substitui a função original com um objeto de classe que tem um método __call__(). Mas um objeto de função também é chamável, então podemos reescrever o exemplo anterior usando uma função, ao invés de uma classe, assim:
def entryExit(f):
def new_f():
print "Entering", f.__name__
f()
print "Exited", f.__name__
return new_f
@entryExit
def func1():
print "inside func1()"
@entryExit
def func2():
print "inside func2()"
func1()
func2()
print func1.__name__
A new_f() é definida dentro do corpo de entryExit(), então ela é criada e retornada quando a entryExit() é chamada. Note que new_f() é um encerramento, porque ela captura o valor real de f.
Uma vez que a new_f() tenha sido definida, ela é retornada a partir de entryExit(), de modo que o mecanismo do decorator possa determinar o resultado como uma função decoradora.
O output da linha print func1.__name__ é new_f, porque a função new_f foi substituída pela função original durante a decoração. Se isso for um problema, você pode mudar o nome da função antes de retorná-la:
def entryExit(f):
def new_f():
print "Entering", f.__name__
f()
print "Exited", f.__name__
new_f.__name__ = f.__name__
return new_f
A informação que você consegue obter dinamicamente sobre as funções, e as modificações que você consegue fazer nelas são bastante poderosas no Python.
Mais exemplos
Agora que você sabe o básico, você pode dar uma olhada em mais exemplos de decorators aqui. Observe o número de exemplos que usam classes, ao invés de funções como decorators.
Neste artigo eu intencionalmente evitei lidar com os argumentos da função decoradora, mas farei isso em um próximo. Arguardem!
***
Texto original disponível em: http://www.artima.com/weblogs/viewpost.jsp?thread=240808