Cuando usas un decorador, estás reemplazando una función con otra. En otras palabras, si tienes un decorador
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
entonces cuando dices
@logged
def f(x):
"""does some math"""
return x + x * x
es exactamente lo mismo que decir
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
y su función f
se reemplaza con la función with_logging
. Desafortunadamente, esto significa que si dices
print(f.__name__)
se imprimirá with_logging
porque ese es el nombre de su nueva función. De hecho, si observa la cadena de documentación f
, estará en blanco porque with_logging
no tiene cadena de documentación, por lo que la cadena de documentación que escribió ya no estará allí. Además, si observa el resultado de pydoc para esa función, no aparecerá como un argumento x
; en su lugar, aparecerá como tomando *args
y **kwargs
porque eso es lo que toma with_logging.
Si usar un decorador siempre significa perder esta información sobre una función, sería un problema grave. Por eso lo tenemos functools.wraps
. Esto toma una función utilizada en un decorador y agrega la funcionalidad de copiar sobre el nombre de la función, la cadena de documentos, la lista de argumentos, etc. Y como wraps
es en sí mismo un decorador, el siguiente código hace lo correcto:
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
este trabajo? ¿No debería ser parte del patrón decorador en primer lugar? ¿Cuándo no quieres usar @wraps?@wraps
para realizar varios tipos de modificación o anotación en los valores copiados. Fundamentalmente, es una extensión de la filosofía de Python que explícito es mejor que implícito y los casos especiales no son lo suficientemente especiales como para romper las reglas. (El código es mucho más simple y el idioma más fácil de entender si@wraps
se debe proporcionar de forma manual, en lugar de utilizar algún tipo de mecanismo especial de exclusión.)Muy a menudo uso clases, en lugar de funciones, para mis decoradores. Estaba teniendo algunos problemas con esto porque un objeto no tendrá los mismos atributos que se esperan de una función. Por ejemplo, un objeto no tendrá el atributo
__name__
. Tuve un problema específico con esto que fue bastante difícil de rastrear donde Django informaba el error "el objeto no tiene atributo '__name__
'". Desafortunadamente, para los decoradores de estilo de clase, no creo que @wrap haga el trabajo. En cambio, he creado una clase de decorador base como esta:Esta clase representa todas las llamadas de atributos a la función que se está decorando. Entonces, ahora puede crear un decorador simple que verifique que se especifiquen 2 argumentos de la siguiente manera:
fuente
@wraps
dice la documentación de ,@wraps
es solo una función convenientefunctools.update_wrapper()
. En el caso del decorador de clase, puede llamarupdate_wrapper()
directamente desde su__init__()
método. Por lo tanto, no es necesario crearDecBase
en absoluto, sólo puede incluir el__init__()
deprocess_login
la línea:update_wrapper(self, func)
. Eso es todo.A partir de python 3.5+:
Es un alias para
g = functools.update_wrapper(g, f)
. Hace exactamente tres cosas:__module__
,__name__
,__qualname__
,__doc__
, y__annotations__
atributos def
sobreg
. Esta lista predeterminada está enWRAPPER_ASSIGNMENTS
, puede verla en la fuente de functools .__dict__
deg
con todos los elementos def.__dict__
. (verWRAPPER_UPDATES
en la fuente)__wrapped__=f
atributo eng
La consecuencia es que
g
parece tener el mismo nombre, cadena de documentación, nombre del módulo y firma quef
. El único problema es que con respecto a la firma, esto no es realmente cierto: es solo queinspect.signature
sigue las cadenas de envoltura por defecto. Puede verificarlo utilizandoinspect.signature(g, follow_wrapped=False)
como se explica en el documento . Esto tiene consecuencias molestas:Signature.bind()
.Ahora hay un poco de confusión entre
functools.wraps
y los decoradores, porque un caso de uso muy frecuente para los decoradores en desarrollo es ajustar las funciones. Pero ambos son conceptos completamente independientes. Si está interesado en comprender la diferencia, implementé bibliotecas auxiliares para ambos: decopatch para escribir decoradores fácilmente y makefun para proporcionar un reemplazo para preservar la firma@wraps
. Tenga en cuenta que semakefun
basa en el mismo truco probado que la famosadecorator
biblioteca.fuente
Este es el código fuente sobre wraps:
fuente
Requisito previo: debe saber cómo usar decoradores y especialmente con envolturas. Este comentario lo explica un poco claro o este enlace también lo explica bastante bien.
Siempre que usamos For eg: @wraps seguido de nuestra propia función wrapper. Según los detalles dados en este enlace , dice que
Entonces, el decorador @wraps realmente llama a functools.partial (func [, * args] [, ** keywords]).
La definición functools.partial () dice que
Lo que me lleva a la conclusión de que @wraps llama a partial () y le pasa la función de contenedor como parámetro. El parcial () al final devuelve la versión simplificada, es decir, el objeto de lo que está dentro de la función de contenedor y no la función de contenedor en sí.
fuente
En resumen, functools.wraps es solo una función regular. Consideremos este ejemplo oficial . Con la ayuda del código fuente , podemos ver más detalles sobre la implementación y los pasos de ejecución de la siguiente manera:
Al verificar la implementación de __call__ , vemos que después de este paso, el contenedor (el lado izquierdo) se convierte en el objeto resultante de self.func (* self.args, * args, ** newkeywords). Al verificar la creación de O1 en __new__ , nosotros saber self.func es la función update_wrapper . Utiliza el parámetro * args , el contenedor del lado derecho , como su primer parámetro. Al verificar el último paso de update_wrapper , se puede ver que se devuelve el contenedor del lado derecho , con algunos de los atributos modificados según sea necesario.
fuente