julho 24 2018 · python

Decoradores em Python

O que são decoradores?

Um decorador nada mais é do que uma função/método ou qualquer objeto Callable que recebe como argumento um objeto que também seja um Callable acrescentando ao mesmo alguma funcionalidade, fazendo alguma verificação, ou qualquer coisa que você ache que deva fazer antes de realmente chamar a função em si. Vamos ver um pouco de código.

def add(a, b):
    return a + b

def sub(a, b):
    return a - b

Digamos que queremos implementar um sistema de log nessas funções faríamos assim:

import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

def add(a, b):
    logger.debug('Função add chamada')
    return a + b

def sub(a, b):
    logger.debug('Função sub chamada')
    return a - b

Acrescentamos log somente a nível de depuração nas duas funções até aí nada de mais mas e se criamos as funções div, mul, ou outras? Teríamos duplicação de código em todas elas, então qual seria a melhor abordagem aqui? Se você respondeu decoradores acertou em cheio. Com o uso de decoradores podemos extrair a funcionalidade comum e deixá-la a cargo do decorador, enquanto a parte variante inplementamos na própria função.

import logging

logger = logging.getLogger()

def decorator(func):
    def wrapper(a, b):
        logger.debug('Função {} chamada'.format(func.__name__))
    return wrapper

@decorator
def add(a, b):
    return a + b

@decorator
def sub(a, b):
    return a - b

Agora não importa quantas funções eu queira adicionar a funcionalidade do log, somente preciso utilizar meu decorador e pronto, a mesma agora possui essa funcionalidade.

Vamos fazer algumas verificações nesse código.

>>> add
<function decorator.<locals>.wrapper at 0x7f259e64d9d8>
>>> add.__name__
'wrapper'

Pelo que podemos ver a função decorada perdeu digamos assim sua "identidade", mas se olharmos novamente nosso código poderemos rapidamente descobrir o motivo não é mesmo? O motivo é que quando decoramos a função estamos chamando o decorador e passando como argumento essa função da seguinte forma:

add = decorator(add) # O mesmo que utilizar @decorator acima da definição da função

A função decorator está sendo executada com o parâmetro func sendo a função add, depois o decorador define um função aninhada(interna) chamada wrapper, e retorna a mesma, ou seja, a função retornada não é a mesma que foi decorada mas sim um invólucro. Um meio de contornar a perda desses atributos, caso você necessite deles para realizar introspecção, ou qualquer que seja o motivo é utilizar a função wraps que acreditem ou não é um decorador.

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(a, b):
        return func(a, b)
    return wrapper

Agora se fizermos:

>>> add = decorator(add)
>>> add
<function add at 0x7f125611dae8>
>>> add.__name__
'add'

Espero ter ajudado nem mesmo que seja um pouco no seu entendimento sobre decoradores, qualquer dúvida deixe nos comentários que tentarei responde-lá da melhor forma possível.