Decoradores_Python_(Python_Decorators)

Está é uma simples introdução preparada para o encontro de usuários Python do Grupo "Greater New Hampshire Linux Users Group".

Funções de primeira classe (First-class functions)

Métodos/Funções em Python são objetos de primeiro nível. Peculiarmente, funções podem ser passadas como valores para outras funções e serem retornadas como valores de outras funções. Por exemplo, aqui está uma função que recebe uma função como argumento e imprime o nome dela na saída padrão:

>>> def show_func(f):
...   print f.__name__

E aqui um simples exemplo de uso:

>>> def foo():
...   print 'foo here'

>>> show_func(foo)
foo

E aqui está uma função que gera uma nova função e a retorna como resultado. Nesse caso, make_adder() cria uma nova função que soma o parâmetro y à constante x, passada na sua criação.

>>> def make_adder(x):
...   def adder(y):
...     return x+y
...   return adder

>>> a = make_adder(5)
>>> a(10)
15

Decorando uma função (Wrapping a function)

Combinando essas duas idéias, nós podemos fazer uma função que recebe uma função como parâmetro e retorna uma nova função que é baseada na função passada. Por exemplo, podemos decorar a função passada em uma nova função que loga todas suas chamadas.

>>> def show_call(f):
...   def shown(*args, **kwds):
...     print 'Calling', f.__name__
...     return f(*args, **kwds)
...   return shown

>>> bar = show_call(foo)
>>> bar()
Calling foo
foo here

Se nós reatribuirmos o resultado de show_foo() para o mesmo nome, nós estamos de fato substituindo a função original pela versão decorada.

>>> foo = show_call(foo)
>>> foo()
Calling foo
foo here

Decoradores são açucar sintático (Decorators are syntactic sugar)

Se você seguiu essa discussão até aqui, então você entende as bases em que se apóiam os decoradores, pois um decorador é apenas um açúcar sintático para o que acabamos de fazer. Especificamente, o código.

   1 @show_call
   2 def foo():
   3   pass

É equivalente a:

   1 def foo():
   2   pass
   3 foo = show_call(foo)

Qualquer função que recebe uma outra função como parâmetro como seu único argumento e retorna uma outra função ou qualquer outro tipo "callable" (qualquer tipo que responda ao método __call__(), como um "descriptor", mas não falaremos deles aqui) pode ser usado como um decorador.

Então, qual é a grande sacada? (So what's the big deal?)

Por um lado não há bem uma "grande sacada"; decoradores não adicionam nada de novo à linguagem. Eles são apenas uma nova sintaxe para uma Idéia antiga. Mas decoradores também mostram como a sintaxe pode fazer diferença – eles são bem populares e muito usados em códigos Python contemporâneos.

Decoradores são usados vastamente para extrair um código comum que deve ser aplicado para diversas funções. Colocando o decorador com uma sintaxe própria, torna a intenção e a função do código muito mais claras.

Decoradores bem comportados (Well-behaved decorators)

Com um simples decorador, como mostrado abaixo, existem notáveis diferenças entre a função original e a função decorada:

>>> def bar():
...   ''' Function bar() '''
...   pass

>>> bar.__name__, bar.__doc__, bar.__module__
('bar', ' Function bar() ', '__main__')

>>> import inspect
>>> inspect.getargspec(bar)
([], None, None, None)

>>> bar2 = show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('shown', None, '__main__')

>>> inspect.getargspec(bar2)
([], 'args', 'kwds', None)

Os atributos da função não são copiados para a função decorada, e a assinatura do método decorado é diferente da original.

Há como preservar os atributos da função original copiando-os da função original para a função decorada. Aqui está uma implementação melhor para "show_call()":

   1 def show_call(f):
   2   def shown(*args, **kwds):
   3     print 'Calling', f.__name__
   4     return f(*args, **kwds)
   5 
   6   shown.__name__ = f.__name__
   7   shown.__doc__ = f.__doc__
   8   shown.__module__ = f.__module__
   9   shown.__dict__.update(f.__dict__)
  10 
  11   return shown

Nesta versão, os atributos estão corretos, porém a assinatura está completamente diferente:

>>> bar2 = show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('bar', ' Function bar() ', '__main__')

O módulo functools (novo no Python 2.5) provê um decorador para decoradores, chamado wraps(), que simplesmente copia os atributos do método original para o novo método. O exemplo acima poderia ser escrito da seguinte maneira:

>>> from functools import wraps

>>> def show_call(f):
...   @wraps(f)
...   def shown(*args, **kwds):
...     print 'Calling', f.__name__
...     return f(*args, **kwds)
...   return shown

>>> bar2 = show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('bar', ' Function bar() ', '__main__')

O módulo decorator de Michele Simionato faz uso do método eval() para criar decoradores com a mesma assinatura do método original.

Decoradores com argumentos (Decorators with arguments)

Você deve ter notado algo novo no exemplo acima: o próprio decorador wraps() recebe um argumento. Como será que isso funciona?

Vamos supor que temos este código:

   1 @wraps(f)
   2 def do_nothing(*args, **kwds):
   3   return f(*args, **kwds)

Pela definição da sintaxe de decoradores, isto é equivalente à:

   1 def do_nothing(*args, **kwds):
   2   return f(*args, **kwds)
   3 
   4 do_nothing = wraps(f)(do_nothing)

Para isto fazer sentido, wraps() deve ser uma fábrica de decoradores – uma função onde o valor de retorno é também um decorador. Em outras palavras, wraps() é uma função cujo retorno é uma função que recebe uma função como argumento e retorna uma função!

Uau!

Que tal um exemplo simples? Podemos fazer um decorador que repetidamente chama a função a qual está decorando? Para um número constante de chamadas, isto é balela:

>>> def repeat3(f):
...   def inner(*args, **kwds):
...     f(*args, **kwds)
...     f(*args, **kwds)
...     return f(*args, **kwds)
...   return inner

>>> f3 = repeat3(foo)
>>> f3()
foo here
foo here
foo here

Mas caso queiramos prover o número de repetições como um argumento? Então precisamos de uma função que tenha como retorno um decorador. O decorador retornado será similar ao repeat3(), acima. Isso requere um novo nível de hierarquia de funções:

>>> def repeat(n):
...   def repeatn(f):
...     def inner(*args, **kwds):
...       for i in range(n):
...         ret = f(*args, **kwds)
...       return ret
...     return inner
...   return repeatn

Aqui repeat() é uma fábrica de decoradores, repeatn() é de fato o decorador, e inner() é o método decorado que será chamado no lugar do original.

N. d. t: A fábrica de decoradores só se faz necessária quando passamos argumentos para o decorador.

Aqui ele sendo usado com a sintaxe de decoradores:

>>> @repeat(4)
... def bar():
...   print 'bar here'

>>> bar()
bar here
bar here
bar here
bar here       

Exemplos Reais (Real-world examples)

Bons exemplos para iniciantes de decoradores do mundo real são difíceis de encontrar. Decoradores tendem a ser bem simples, estes não adicionam muito aos exemplos acima, ou significativamente mais complexos e difíceis de entender.

E decoradores do mundo real são normalmente compostos de outros decoradores e não são únicos, trabalham juntos.

A biblioteca de decoradores Python Decorator Library é uma boa fonte de exemplos simples.

O decorador Synchronize é um dos mais simples. O Easy Dump of Function Arguments é uma versão mais completas do show_call(), usado acima.

O TurboGears usa extensivamente o seu decorador @expose para configurar a saída de métodos que são expostos para a web. O código está aqui.

Próximas leituras (Further reading)

What's new in Python 2.4 – Dá uma explanação breve sobre o que são os decoradores.

PEP-318 é a formalização da sintaxe dos decoradores.

A biblioteca de decoradores Python Decorator Library tem vários exemplos interessantes.

A documentação para o módulo decorator de Michele Simionato tem uma descrição do problema de criar decoradores bem comportados e vários exemplos de decoradores. O próprio módulo provê uma maneira de criar decoradores que preservam a assinatura dos métodos decorados.

David Mertz escreveu um coluna para o Charming Python chamado "Decorators make magic easy".


Artigo traduzido com a devida autorização do autor, Kent Johnson, do site: http://personalpages.tds.net/~kent37/kk/00001.html por Ruivaldo Neto. Revisão e formatação por OsvaldoSantanaNeto

Decoradores_Python_(Python_Decorators) (editada pela última vez em 2016-09-30 18:41:16 por Thiago Reis)