sexta-feira, 22 de dezembro de 2017

Decorators em Python - parte 3


Decorando métodos e passando parâmetros nos decorators

Continuação de Decorators em Python - parte 2 

Decorando Métodos

Em Python, métodos são funções que esperam que seu primeiro parâmetro seja uma referência ao objeto atual. Métodos são funções dentro de classes. Podemos criar decoradores para métodos da mesma maneira que criamos para uma função simples. Vamos usar o p_decorate usado nos nossos exemplos anteriores para decorar uma função que retorna o nome completo de uma pessoa.

def p_decorate(func):
   def func_wrapper(self):
       return "<p>{}</p>".format(func(self))
   return func_wrapper

class Pessoa(object):
    def __init__(self, nome, sobrenome):
        self.nome = nome
        self.sobrenome = sobrenome

    @p_decorate
    def get_nome_completo(self):
        return self.nome + " " + self.sobrenome

pessoa = Pessoa('Francisco', 'Lima')
print(pessoa.get_nome_completo())
<p>Francisco Lima</p>

Passando argumentos nos decoradores

Uma abordagem bem melhor seria tornar nosso decorador útil para funções e métodos. Isso pode ser feito colocando *args e *kwargs como parâmetros para o wrapper, então ele pode aceitar qualquer número arbitrário de argumentos e argumentos de palavras-chave.

def p_decorate(func):
    def func_wrapper(*args, **kwargs):
       return "<p>{0}</p>".format(func(*args, **kwargs))
    return func_wrapper

class Pessoa(object):
    def __init__(self, nome, sobrenome):
        self.nome = nome
        self.sobrenome = sobrenome

    @p_decorate
    def get_nome_completo(self):
        return self.nome + " " + self.sobrenome

pessoa = Pessoa('Francisco', 'Lima')
print(pessoa.get_nome_completo())
<p>Francisco Lima</p>

Vamos ver novamente o exemplo dos 3 decoradores, p, strong e div que usamos na parte 2 deste Post.

def p_decorate(func):
    def func_wrapper(nome):
        return "<p>{}</p>".format(func(nome))
    return func_wrapper

def strong_decorate(func):
    def func_wrapper(name):
        return "<strong>{0}</strong>".format(func(name))
    return func_wrapper

def div_decorate(func):
    def func_wrapper(name):
        return "<div>{0}</div>".format(func(name))
    return func_wrapper

@div_decorate
@p_decorate
@strong_decorate
def get_text(nome):
    return "Olá {0}, seja bem vindo ao nosso site!".format(nome)
print (get_text("John"))
<div><p><strong>Olá John, seja bem vindo ao nosso site!</strong></p></div>

Olhando para ele, podemos notar que os 3 decoradores, div_decorate, p_decorate, strong_decorate, são redundantes, cada um possuí basicamente a mesma funcionalidade, apenas envolvendo a string com diferentes tags.
Dá para fazer muito melhor do que isso. Por que não ter uma implementação mais geral para um decorator que leva a tag para envolver a string? Pois é isso que vamos fazer.

def tags(tag_name):
    def tags_decorator(func):
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator

@tags("div")
@tags("p")
@tags("strong")
def get_text(nome):
    return "Olá {0}, seja bem vindo ao nosso site!".format(nome)

print(get_text("Francisco"))
<div><p><strong>Olá Francisco, seja bem vindo ao nosso site!</strong></p></div>

Usando um único decorator, substituimos os 3 anteriores. É claro que tivemos um pouco mais de trabalho neste caso. Os decoradores esperam receber uma função como argumento, e é por isso que tivemos que construir uma função que receba esses argumentos extras para gerar o nosso decorador sobre ela.
No exemplo acima, @tags , é o nosso gerador do decorador.

Jupyter notebook contendo o código deste estudo:
github.com/FranciscoACLima/Python-decorators

Mais sobre o uso de decoradores em Python:
thecodeship.com/patterns/guide-to-python-function-decorators/

Até...
Francisco ACLima

Nenhum comentário:

Postar um comentário