Na Parte 01, nós vimos o envio de funções como argumentos para outras funções, em funções agrupadas e, finalmente, empacotar uma função em outra. Vamos começar esta parte, dando um exemplo de implementação para o exercício que eu dei na Parte 01:
System Message: ERROR/3 (<string>, line 11)
Unknown directive type "code".
.. code:: python
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs):
... print("Calling %s with arguments: \n\targs: %s\n\tkwargs:%s" % (
... fn.__name__, args, kwargs))
... retval = fn(*args, **kwargs)
... print("%s returning '%s'" % (fn.func_name, retval))
... return retval
... fn_wrap.func_name = fn.func_name
... return fn_wrap
...
>>> def greeter(greeting, what='world'):
... return "%s %s!" % (greeting, what)
...
>>> greeter = print_call(greeter)
>>> greeter("Hi")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{}
greeter returning 'Hi world!'
'Hi world!'
>>> greeter("Hi", what="Python")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{'what': 'Python'}
greeter returning 'Hi Python!'
'Hi Python!'
>>>
Então, isso é no mínimo ligeiramente útil, mas vai ficar melhor! Você pode ou não ter ouvido falar de closures, e você pode ter ouvido de um grande número de definições do que uma closure é – eu não vou entrar em picuinhas, mas apenas dizer que uma closure é um bloco de código (por exemplo, uma função), que capta variáveis (ou faz um close over) não-locais (livre). Se tudo isso é meio sem lógica ou papo furado para você, provavelmente você está precisando de uma reciclagem, mas não tenha medo – eu vou mostrar, através de exemplo, e o conceito é bastante fácil de entender: uma função pode fazer referência a variáveis que são definidas no escopo de fechamento da função.
Por exemplo, dê uma olhada neste código:
System Message: ERROR/3 (<string>, line 54)
Unknown directive type "code".
.. code:: python
>>> a = 0
>>> def get_a():
... return a
...
>>> get_a()
0
>>> a = 3
>>> get_a()
3
Como você pode ver, a função get_a pode obter o valor de a, e será capaz de ler o valor atualizado. No entanto, existe uma limitação – uma variável capturada não pode ser escrita para:
System Message: ERROR/3 (<string>, line 70)
Unknown directive type "code".
.. code:: python
>>> def set_a(val):
... a = val
...
>>> set_a(4)
>>> a
3
O que aconteceu aqui? Desde que uma closure não pode escrever todas as variáveis capturadas, a = val realmente escreve para uma variável local a que acompanha o nível de módulo a que queríamos escrever. Para contornar essa limitação (o que pode ou não ser uma boa ideia), podemos usar um tipo de recipiente:
System Message: ERROR/3 (<string>, line 85)
Unknown directive type "code".
.. code:: python
>>> class A(object): pass
...
>>> a = A()
>>> a.value = 1
>>> def set_a(val):
... a.value = val
...
>>> a.value
1
>>> set_a(5)
>>> a.value
5
Assim, com o conhecimento de que uma função captura variáveis a partir de seu escopo de fechamento, finalmente estamos aproximando de algo interessante, e vamos começar pela implementação de um partial. Um partial é uma instância de uma função na qual você já preencheu alguns ou todos os argumentos; digamos, por exemplo, que você tem uma sessão com nome de usuário e senha armazenada, e uma função que consulta alguma camada de backend que leva argumentos diferentes, mas sempre requer credenciais. Em vez de passar as credenciais manualmente toda vez, podemos usar um partial para pré-preencher os valores:
System Message: ERROR/3 (<string>, line 110)
Unknown directive type "code".
.. code:: python
>>> #Our 'backend' function
... def get_stuff(user, pw, stuff_id):
... """Here we would presumably fetch data using the supplied
... credentials and id"""
... print("get_stuff called with user: %s, pw: %s, stuff_id: %s" % (
... user, pw, stuff_id))
>>> def partial(fn, *args, **kwargs):
... def fn_part(*fn_args, **fn_kwargs):
... kwargs.update(fn_kwargs)
... return fn(*args + fn_args, **kwargs)
... return fn_part
...
>>> my_stuff = partial(get_stuff, 'myuser', 'mypwd')
>>> my_stuff(3)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 3
>>> my_stuff(67)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 67
Partials podem ser utilizados em vários lugares para remover duplicação de código em que uma função é chamada em lugares diferentes com os mesmos, ou quase os mesmos, argumentos. Claro, você não precisa implementá-lo, basta fazer from functools import partial.
Finalmente, vamos dar uma olhada em função decorators. Uma função decorator é (pode ser implementada como) uma função que recebe uma função como parâmetro e retorna uma nova. Soa familiar? Deveria, porque já implementamos um working decorator: a nossa função print_call está pronta para ser usada como está:
System Message: ERROR/3 (<string>, line 142)
Unknown directive type "code".
.. code:: python
>>> @print_call
... def will_be_logged(arg):
... return arg*5
...
>>> will_be_logged("!")
Calling will_be_logged with arguments:
args: ('!',)
kwargs:{}
will_be_logged returning '!!!!!'
'!!!!!'
Usar a notação @ é simplesmente uma abreviação conveniente para fazer:
System Message: ERROR/3 (<string>, line 157)
Unknown directive type "code".
.. code:: python
>>> def will_be_logged(arg):
... return arg*5
...
>>> will_be_logged = print_call(will_be_logged)
Mas e se quisermos ser capazes de parametrizar o decorator? Nesse caso, a função utilizada como um decorator receberá os argumentos, e deverá retornar uma função que envolve a função decorated:
System Message: ERROR/3 (<string>, line 169)
Unknown directive type "code".
.. code:: python
>>> def require(role):
... def wrapper(fn):
... def new_fn(*args, **kwargs):
... if not role in kwargs.get('roles', []):
... print("%s not in %s" % (role, kwargs.get('roles', [])))
... raise Exception("Unauthorized")
... return fn(*args, **kwargs)
... return new_fn
... return wrapper
...
>>> @require('admin')
... def get_users(**kwargs):
... return ('Alice', 'Bob')
...
>>> get_users()
admin not in []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'editor'])
admin not in ['user', 'editor']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'admin'])
('Alice', 'Bob')
E aí está. Agora você está pronto para escrever decorators, e talvez usá-los para escrever aspectos orientados a Python; acrescentar @cache, @trace, @throtlle é bem trivial (e antes que você adicione @cache, faça a verificação functools mais uma vez se você estiver usando Python 3!).
?
Texto original disponível em http://blaag.haard.se/Python-Closures-and-Decorators–Pt–2/







