Desde que, em retrospecto, fiz a escolha errada quando reduzi um curso de Python para quatro horas e baguncei com o exercício do decorator, prometi aos participantes do curso que escreveria um artigo sobre closures e decorators para explicá-los melhor – esta é a minha tentativa de o fazer.
Funções também são objetos. Na verdade, em Python são objetos de primeira classe – ou seja, eles podem ser tratados como qualquer outro objeto sem restrições especiais. Isso nos dá algumas opções interessantes, e eu vou tentar abordá-las sob todos os aspectos.
Um caso muito básico do fato de que funções são objetos é usá-las como se fosse um ponteiro de função em C; passá-la para outra função que irá utilizá-la. Para ilustrar isso, vamos dar uma olhada na implementação de uma função de repetição – ou seja, uma função que aceita outra como argumento em junto com um número, e depois chama a função passada um específico número de vezes:
>>> #A very simple function
>>> def greeter():
... print("Hello")
...
>>> #An implementation of a repeat function
>>> def repeat(fn, times):
... for i in range(times):
... fn()
...
>>> repeat(greeter, 3)
Hello
Hello
Hello
>>>
Esse padrão é utilizado em um grande número de maneiras – passando uma função de comparação para um algoritmo de classificação, passando uma função de decodificação para um parser e, em geral, especializando o comportamento de uma função, ou passando partes específicas de um trabalho a ser feito para uma função que abstrai o fluxo de trabalho (por exemplo, sort () sabe como classificar listas, compare () sabe como comparar elementos).
As funções também podem ser declaradas no corpo de outra função, o que nos dá outra ferramenta importante. No caso mais básico, isso pode ser usado para “esconder” funções utilitárias no âmbito da função que as utiliza:
>>> def print_integers(values):
... def is_integer(value):
... try:
... return value == int(value)
... except:
... return False
... for v in values:
... if is_integer(v):
... print(v)
...
>>> print_integers([1,2,3,"4", "parrot", 3.14])
1
2
3
Isso pode ser útil, mas dificilmente é em si uma ferramenta muito poderosa. Comparado com o fato de que as funções podem ser passadas como argumentos, podemos adicionar comportamentos a funções depois que elas são construídas, ao empacotá-las em outra função. Um exemplo simples seria adicionar uma saída de rastreamento para uma função:
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs): #take any arguments
... print("Calling %s" % (fn.func_name))
... return fn(*args, **kwargs) #pass any arguments to fn()
... return fn_wrap
...
>>> greeter = print_call(greeter) #wrap greeter
>>> repeat(greeter, 3)
Calling fn_wrap
Hello
Calling fn_wrap
Hello
Calling fn_wrap
Hello
>>>
>>> greeter.func_name
'fn_wrap'
Como você pode ver, podemos substituir a função greeter com uma nova função que usa print para registrar a chamada, e depois chama a função original. Como se vê nas duas últimas linhas do exemplo, o nome da função reflete que ela foi substituída, o que pode ou não ser o que nós quisemos. Se quisermos empacotar uma função, mantendo o nome original, podemos fazê-lo adicionando uma linha à nossa função print_call:
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs): #take any arguments
... print("Calling %s" % (fn.func_name))
... return fn(*args, **kwargs) #pass any arguments to fn()
... fn_wrap.func_name = fn.func_name #Copy the original name
... return fn_wrap
Uma vez que este está se transformando em um artigo muito longo, vou parar por aqui e voltar em breve com a parte dois, na qual veremos closures, partials, e (finalmente) decorators.
Até lá, se tudo isso for novo para você, use print_call como base para criar uma função que irá imprimir o nome da função e argumentos passados antes de chamar a função empacotada, e o nome da função e o valor de retorno antes de retornar.
?
Texto original disponível em http://blaag.haard.se/Python-Closures-and-Decorators–Pt–1/