Como posso criar dois decoradores em Python que fazem o seguinte?
@makebold
@makeitalic
def say():
return "Hello"
... que deve retornar:
"<b><i>Hello</i></b>"
Não estou tentando fazer HTML
isso em um aplicativo real - apenas tentando entender como os decoradores e o encadeamento de decoradores funcionam.
__name__
e, falando sobre o pacote decorador, assinatura da função).*args
e**kwargs
deve ser adicionado na resposta. A função decorada pode ter argumentos e eles serão perdidos se não forem especificados.*args
,**kwargs
. Uma maneira fácil de resolver esses três problemas de uma vez é usardecopatch
como explicado aqui . Você também pode usardecorator
como já mencionado por Marius Gedminas, para resolver os pontos 2 e 3.Se você não tiver longas explicações, consulte a resposta de Paolo Bergantino .
Noções básicas do decorador
As funções do Python são objetos
Para entender os decoradores, você deve primeiro entender que funções são objetos em Python. Isso tem consequências importantes. Vamos ver o porquê com um exemplo simples:
Mantenha isso em mente. Voltaremos a ele em breve.
Outra propriedade interessante das funções do Python é que elas podem ser definidas dentro de outra função!
Referências de funções
Ok, ainda está aqui? Agora a parte divertida ...
Você viu que funções são objetos. Portanto, funções:
Isso significa que uma função pode
return
outra função .Tem mais!
Se você pode
return
uma função, pode passar uma como parâmetro:Bem, você só tem tudo o necessário para entender os decoradores. Veja, decoradores são "invólucros", o que significa que permitem executar o código antes e depois da função que decoram sem modificar a própria função.
Decoradores artesanais
Como você faria isso manualmente:
Agora, você provavelmente deseja que toda vez que ligar
a_stand_alone_function
,a_stand_alone_function_decorated
seja chamado. Isso é fácil, basta substituira_stand_alone_function
a função retornada pormy_shiny_new_decorator
:Decoradores desmistificados
O exemplo anterior, usando a sintaxe do decorador:
Sim, é tudo, é simples assim.
@decorator
é apenas um atalho para:Decoradores são apenas uma variante pitônica do padrão de design do decorador . Existem vários padrões de design clássicos incorporados no Python para facilitar o desenvolvimento (como iteradores).
Obviamente, você pode acumular decoradores:
Usando a sintaxe do decorador Python:
A ordem em que você define os decoradores é IMPORTANTE:
Agora: para responder à pergunta ...
Como conclusão, você pode ver facilmente como responder à pergunta:
Agora você pode simplesmente sair feliz ou queimar seu cérebro um pouco mais e ver usos avançados de decoradores.
Levando os decoradores para o próximo nível
Passando argumentos para a Função Decorada
Métodos de decoração
Uma coisa bacana do Python é que métodos e funções são realmente os mesmos. A única diferença é que os métodos esperam que o primeiro argumento seja uma referência ao objeto atual (
self
).Isso significa que você pode criar um decorador para métodos da mesma maneira! Lembre-se de levar
self
em consideração:Se você estiver criando um decorador de uso geral - um que você aplicará a qualquer função ou método, independentemente dos argumentos -, basta usar
*args, **kwargs
:Passando argumentos para o Decorador
Ótimo, agora o que você diria sobre passar argumentos para o próprio decorador?
Isso pode ficar um pouco distorcido, pois um decorador deve aceitar uma função como argumento. Portanto, você não pode passar os argumentos da função decorada diretamente para o decorador.
Antes de correr para a solução, vamos escrever um pequeno lembrete:
É exatamente o mesmo. "
my_decorator
" é chamado. Então, quando você@my_decorator
está dizendo ao Python para chamar a função 'rotulada pela variável "my_decorator
"'.Isso é importante! A etiqueta que você fornecer pode apontar diretamente para o decorador - ou não .
Vamos ficar mal. ☺
Nenhuma surpresa aqui.
Vamos fazer exatamente a mesma coisa, mas pular todas as variáveis intermediárias traquinas:
Vamos torná-lo ainda mais curto :
Ei, você viu isso? Usamos uma chamada de função com a
@
sintaxe " "! :-)Então, voltando aos decoradores com argumentos. Se podemos usar funções para gerar o decorador em tempo real, podemos passar argumentos para essa função, certo?
Aqui está: um decorador com argumentos. Os argumentos podem ser definidos como variáveis:
Como você pode ver, você pode passar argumentos para o decorador como qualquer função usando esse truque. Você pode até usar
*args, **kwargs
se desejar. Mas lembre-se de que os decoradores são chamados apenas uma vez . Apenas quando o Python importa o script. Você não pode definir dinamicamente os argumentos depois. Quando você "importa x", a função já está decorada , portanto você não pode alterar nada.Vamos praticar: decorar um decorador
Certo, como bônus, darei um trecho para que qualquer decorador aceite genericamente qualquer argumento. Afinal, para aceitar argumentos, criamos nosso decorador usando outra função.
Envolvemos o decorador.
Mais alguma coisa que vimos recentemente nessa função embrulhada?
Oh sim, decoradores!
Vamos nos divertir e escrever um decorador para os decoradores:
Pode ser usado da seguinte maneira:
Eu sei, a última vez que você teve esse sentimento, foi depois de ouvir um cara dizendo: "antes de entender a recursão, você deve primeiro entender a recursão". Mas agora, você não se sente bem em dominar isso?
Práticas recomendadas: decoradores
O
functools
módulo foi introduzido no Python 2.5. Inclui a funçãofunctools.wraps()
, que copia o nome, o módulo e a sequência de caracteres da função decorada para seu invólucro.(Curiosidade:
functools.wraps()
é um decorador! ☺)Como os decoradores podem ser úteis?
Agora a grande pergunta: para que posso usar decoradores?
Parece legal e poderoso, mas um exemplo prático seria ótimo. Bem, existem 1000 possibilidades. Os usos clássicos estão estendendo o comportamento de uma função de uma biblioteca externa (você não pode modificá-la) ou para depuração (você não deseja modificá-la porque é temporária).
Você pode usá-los para estender várias funções no modo DRY, como:
É claro que o bom dos decoradores é que você pode usá-los imediatamente em quase tudo sem precisar reescrever. SECO, eu disse:
O próprio Python fornece vários decoradores:
property
,staticmethod
, etc.Este é realmente um grande parque infantil.
fonte
__closure__
atributo) para retirar a função não decorada original. Um exemplo de uso está documentado nesta resposta, que aborda como é possível injetar uma função decoradora em um nível inferior em circunstâncias limitadas.@decorator
sintaxe do Python é provavelmente usada com mais frequência para substituir uma função por um fechamento de wrapper (como a resposta descreve). Mas também pode substituir a função por outra coisa. O embutidoproperty
,classmethod
estaticmethod
decoradores substituir a função com um descritor, por exemplo. Um decorador também pode fazer algo com uma função, como salvar uma referência a ele em um registro de algum tipo e depois devolvê-lo, sem modificação, sem nenhum invólucro.Como alternativa, você pode escrever uma função de fábrica que retorne um decorador que agrupe o valor de retorno da função decorada em um tag passado para a função de fábrica. Por exemplo:
Isso permite que você escreva:
ou
Pessoalmente, eu teria escrito ao decorador um pouco diferente:
o que produziria:
Não se esqueça da construção para a qual a sintaxe do decorador é uma abreviação:
fonte
def wrap_in_tag(*kwargs)
então@wrap_in_tag('b','i')
Parece que as outras pessoas já lhe disseram como resolver o problema. Espero que isso ajude você a entender o que são decoradores.
Decoradores são apenas açúcar sintático.
este
expande para
fonte
@decorator()
(em vez de@decorator
) é açúcar sintático parafunc = decorator()(func)
. Esta é também uma prática comum quando você precisa para gerar decoradores "on the fly"E é claro que você pode retornar lambdas também a partir de uma função decoradora:
fonte
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
para não descartar o docstring / assinatura / nome desay
@wraps
em outro lugar nessa página não vai me ajudar quando eu imprimirhelp(say)
e obter "Ajuda na função <lambda>` em vez de 'Ajuda na função digamos' .Decoradores Python adicionam funcionalidade extra a outra função
Um decorador em itálico pode ser como
Observe que uma função é definida dentro de uma função. O que basicamente faz é substituir uma função pela recém-definida. Por exemplo, eu tenho essa classe
Agora diga, quero que ambas as funções imprimam "---" depois e antes de serem concluídas. Eu poderia adicionar uma impressão "---" antes e depois de cada instrução de impressão. Mas como não gosto de me repetir, farei um decorador
Então agora eu posso mudar minha classe para
Para obter mais informações sobre decoradores, consulte http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
fonte
self
argumento é necessário porque onewFunction()
definido emaddDashes()
foi especificamente projetado para ser um decorador de método e não um decorador de função geral. Oself
argumento representa a instância da classe e é passado aos métodos da classe, independentemente de serem usados ou não - consulte a seção intitulada Decorando métodos na resposta do @ e-satis.functools.wraps
Você pode criar dois decoradores separados que fazem o que você deseja, conforme ilustrado diretamente abaixo. Observe o uso de
*args, **kwargs
na declaração dowrapped()
função que suporta a função decorada com vários argumentos (o que não é realmente necessário para asay()
função de exemplo , mas está incluído para generalidade).Por razões semelhantes, o
functools.wraps
decorador é usado para alterar os meta atributos da função agrupada para serem aqueles da que está sendo decorada. Isso faz com que as mensagens de erro e a documentação da função incorporada (func.__doc__
) sejam aquelas da função decorada em vez dewrapped()
's.Refinamentos
Como você pode ver, há muitos códigos duplicados nesses dois decoradores. Dada essa semelhança, seria melhor você criar uma genérica que na verdade fosse uma fábrica de decoradores - em outras palavras, uma função decoradora que produz outros decoradores. Dessa forma, haveria menos repetição de código - e permitiria que o princípio DRY fosse seguido.
Para tornar o código mais legível, você pode atribuir um nome mais descritivo aos decoradores gerados de fábrica:
ou até combiná-los assim:
Eficiência
Enquanto os exemplos acima fazem todo o trabalho, o código gerado envolve uma boa quantidade de sobrecarga na forma de chamadas de funções estranhas quando vários decoradores são aplicados ao mesmo tempo. Isso pode não importar, dependendo do uso exato (que pode ser vinculado à E / S, por exemplo).
Se a velocidade da função decorada é importante, a sobrecarga pode ser mantida em uma única chamada de função extra, escrevendo uma função de fábrica decoradora ligeiramente diferente que implementa a adição de todas as tags de uma só vez, para gerar código que evita as chamadas de função adicionais incorridas usando decoradores separados para cada tag.
Isso requer mais código no próprio decorador, mas isso só é executado quando está sendo aplicado às definições de função, e não mais tarde quando elas próprias são chamadas. Isso também se aplica ao criar nomes mais legíveis usando
lambda
funções como ilustrado anteriormente. Amostra:fonte
Outra maneira de fazer a mesma coisa:
Ou, de maneira mais flexível:
fonte
functools.update_wrapper
para mantersayhi.__name__ == "sayhi"
Você deseja a seguinte função, quando chamada:
Para retornar:
Solução simples
Para simplificar, faça decoradores que retornem lambdas (funções anônimas) que fechem sobre a função (closures) e chame-a:
Agora use-os conforme desejado:
e agora:
Problemas com a solução simples
Mas parece que quase perdemos a função original.
Para encontrá-lo, precisamos cavar o fechamento de cada lambda, um dos quais está enterrado no outro:
Portanto, se colocarmos a documentação nessa função, ou desejarmos decorar funções que levam mais de um argumento, ou apenas quisermos saber qual função estávamos observando em uma sessão de depuração, precisamos fazer um pouco mais com o nosso embrulho.
Solução completa - superando a maioria desses problemas
Temos o decorador
wraps
dofunctools
módulo na biblioteca padrão!É lamentável que ainda exista algum boilerplate, mas isso é o mais simples que podemos fazer.
No Python 3, você também obtém
__qualname__
e__annotations__
atribui por padrão.Então agora:
E agora:
Conclusão
Então nós vemos isso
wraps
faz com a função de empacotamento faça quase tudo, exceto nos diga exatamente o que a função usa como argumentos.Existem outros módulos que podem tentar resolver o problema, mas a solução ainda não está na biblioteca padrão.
fonte
Para explicar o decorador de uma maneira simples:
Com:
Quando fazer:
Você realmente faz:
fonte
Um decorador pega a definição da função e cria uma nova função que executa essa função e transforma o resultado.
é equivalente a:
Exemplo:
este
é equivalente a isso
65 <=> 'a'
Para entender o decorador, é importante notar que esse decorador criou uma nova função que é interna que executa a função e transforma o resultado.
fonte
print(do(65))
eprint(do2(65))
beA
e nãoA
?Você também pode escrever decorador em Class
fonte
Esta resposta já foi respondida há muito tempo, mas pensei em compartilhar minha classe Decorator, o que torna a redação de novos decoradores fácil e compacta.
Por um lado, acho que isso torna o comportamento dos decoradores muito claro, mas também facilita a definição de novos decoradores de maneira muito concisa. Para o exemplo listado acima, você pode resolvê-lo como:
Você também pode usá-lo para realizar tarefas mais complexas, como, por exemplo, um decorador que automaticamente faz com que a função seja aplicada recursivamente a todos os argumentos em um iterador:
Que imprime:
Observe que este exemplo não incluiu o
list
tipo na instanciação do decorador; portanto, na declaração de impressão final, o método é aplicado à própria lista, não aos elementos da lista.fonte
Aqui está um exemplo simples de encadear decoradores. Observe a última linha - mostra o que está acontecendo debaixo das cobertas.
A saída se parece com:
fonte
Falando no exemplo do contador - como indicado acima, o contador será compartilhado entre todas as funções que usam o decorador:
Dessa forma, seu decorador pode ser reutilizado para diferentes funções (ou usado para decorar a mesma função várias vezes:)
func_counter1 = counter(func); func_counter2 = counter(func)
, e a variável do contador permanecerá privada para cada uma.fonte
Decore funções com diferentes números de argumentos:
Resultado:
fonte
def wrapper(*args, **kwargs):
efn(*args, **kwargs)
.A resposta de Paolo Bergantino tem a grande vantagem de usar apenas o stdlib e funciona para este exemplo simples, onde não há argumentos de decorador nem argumentos de função decorados .
No entanto, há três grandes limitações se você quiser lidar com casos mais gerais:
makestyle(style='bold')
decorador não é trivial.@functools.wraps
não preservam a assinatura ; portanto, se forem fornecidos argumentos incorretos, eles começarão a ser executados e poderão gerar um tipo de erro diferente do habitualTypeError
.@functools.wraps
a acessar um argumento baseado em seu nome . De fato, o argumento pode aparecer em*args
, dentro**kwargs
ou pode não aparecer (se for opcional).Escrevi
decopatch
para resolver o primeiro problema e escrevimakefun.wraps
para resolver os outros dois. Observe quemakefun
utiliza o mesmo truque que o famosodecorator
lib.É assim que você criaria um decorador com argumentos, retornando invólucros que preservam verdadeiramente a assinatura:
decopatch
fornece outros dois estilos de desenvolvimento que ocultam ou mostram os vários conceitos de python, dependendo de suas preferências. O estilo mais compacto é o seguinte:Nos dois casos, você pode verificar se o decorador funciona conforme o esperado:
Por favor, consulte a documentação para obter detalhes.
fonte