Quando você usa um decorador, substitui uma função por outra. Em outras palavras, se você tem um decorador
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
então quando você diz
@logged
def f(x):
"""does some math"""
return x + x * x
é exatamente o mesmo que dizer
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
e sua função f
é substituído com a função with_logging
. Infelizmente, isso significa que se você diz
print(f.__name__)
será impresso with_logging
porque esse é o nome da sua nova função. De fato, se você olhar para a sequência de caracteres f
, ela ficará em branco porque with_logging
não possui nenhuma sequência e, portanto, a sequência que você escreveu não estará mais lá. Além disso, se você observar o resultado do pydoc para essa função, ele não será listado como tendo um argumento x
; em vez disso, será listado como take *args
e **kwargs
porque é isso que with_logging leva.
Se usar um decorador sempre significasse perder essas informações sobre uma função, seria um problema sério. É por isso que temos functools.wraps
. Isso pega uma função usada em um decorador e adiciona a funcionalidade de copiar sobre o nome da função wraps
, a sequência de documentos, a lista de argumentos etc. E, como ele próprio é um decorador, o código a seguir faz a coisa correta:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
para este trabalho, não deveria ser apenas parte do padrão decorador? quando você não gostaria de usar o @wraps?@wraps
para executar vários tipos de modificação ou anotação nos valores copiados. Fundamentalmente, é uma extensão da filosofia Python que explícito é melhor que implícito e casos especiais não são especiais o suficiente para quebrar as regras. (O código é muito mais simples ea linguagem fácil de entender se@wraps
devem ser fornecidos manualmente, em vez de usar algum tipo de mecanismo especial opt-out.)Costumo usar classes, em vez de funções, para meus decoradores. Eu estava tendo algum problema com isso porque um objeto não terá os mesmos atributos que se espera de uma função. Por exemplo, um objeto não terá o atributo
__name__
. Eu tive um problema específico com isso que era muito difícil de rastrear onde o Django estava relatando o erro "objeto não tem atributo '__name__
'". Infelizmente, para os decoradores de classe, não acredito que o @wrap faça o trabalho. Em vez disso, criei uma classe de decorador de base da seguinte forma:Esta classe proxies todos os atributos chama para a função que está sendo decorada. Portanto, agora você pode criar um decorador simples que verifique se 2 argumentos são especificados da seguinte maneira:
fonte
@wraps
diz,@wraps
é apenas uma função de conveniência parafunctools.update_wrapper()
. No caso do decorador de classe, você pode chamarupdate_wrapper()
diretamente do seu__init__()
método. Assim, você não precisa criarDecBase
em tudo, você pode simplesmente incluir em__init__()
deprocess_login
linha:update_wrapper(self, func)
. Isso é tudo.A partir do python 3.5+:
É um alias para
g = functools.update_wrapper(g, f)
. Faz exatamente três coisas:__module__
,__name__
,__qualname__
,__doc__
, e__annotations__
atributos def
ong
. Esta lista padrão está dentroWRAPPER_ASSIGNMENTS
, você pode vê-la na fonte de functools .__dict__
deg
com todos os elementos def.__dict__
. (vejaWRAPPER_UPDATES
na fonte)__wrapped__=f
atributo emg
A conseqüência é que
g
parece ter o mesmo nome, sequência de caracteres, nome do módulo e assinatura quef
. O único problema é que, com relação à assinatura, isso não é realmente verdade: é apenas o queinspect.signature
segue as cadeias de wrapper por padrão. Você pode verificar usandoinspect.signature(g, follow_wrapped=False)
o explicado no documento . Isso tem consequências irritantes:Signature.bind()
.Agora, há um pouco de confusão entre
functools.wraps
decoradores, porque um caso de uso muito frequente para desenvolver decoradores é agrupar funções. Mas ambos são conceitos completamente independentes. Se você estiver interessado em entender a diferença, implementei bibliotecas auxiliares para ambos: decopatch para escrever decoradores facilmente e makefun para fornecer um substituto que preserva a assinatura@wraps
. Observe que semakefun
baseia no mesmo truque comprovado que a famosadecorator
biblioteca.fonte
Este é o código fonte sobre os envoltórios:
fonte
Pré-requisito: você deve saber como usar decoradores e, especialmente, com envoltórios. Este comentário explica um pouco claro ou este link também explica muito bem.
Sempre que usamos For, por exemplo: @wraps, seguido de nossa própria função de wrapper. De acordo com os detalhes fornecidos neste link , ele diz que
Então, o decorador @wraps na verdade chama uma função para functools.partial (func [, * args] [, ** keywords]).
A definição functools.partial () diz que
O que me leva à conclusão de que o @wraps chama o parcial () e passa a função do wrapper como parâmetro para ele. O parcial () no final retorna a versão simplificada, ou seja, o objeto que está dentro da função de wrapper e não a função de wrapper em si.
fonte
Em resumo, functools.wraps é apenas uma função regular. Vamos considerar este exemplo oficial . Com a ajuda do código fonte , podemos ver mais detalhes sobre a implementação e as etapas em execução da seguinte maneira:
Verificando a implementação de __call__ , vemos que, após esta etapa, o wrapper (do lado esquerdo) se torna o objeto resultante de self.func (* self.args, * args, ** newkeywords) Verificando a criação de O1 em __new__ , nós know self.func é a função update_wrapper . Ele usa o parâmetro * args , o invólucro do lado direito , como seu primeiro parâmetro. Verificando a última etapa do update_wrapper , é possível ver o retorno do wrapper do lado direito , com alguns dos atributos modificados conforme necessário.
fonte