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↳Back-end49 conteúdosIntegração front-end com backend: 7 decisões que evitam caos entre APIs, BFF e GraphQLDev (Back & Front) · abr 2026Como criar uma FAKE API REST para testes — JSONPlaceholderDev (Back & Front) · set 2025Construindo um aplicativo de bate-papo de IA simples com Spring AI e AngularDev (Back & Front) · jul 2025Ver tudo em Dev (Back & Front) → 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/





