Contamos con la mejor respuesta que encontramos en internet. Queremos que te resulte útil y si deseas compartir algún detalle que nos pueda ayudar a perfeccionar nuestra información hazlo con total libertad.
Solución:
Cuando usa un decorador, está reemplazando una función por 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 tu función f
se reemplaza con la función with_logging
. Desafortunadamente, esto significa que si luego dices
print(f.__name__)
se imprimirá with_logging
porque ese es el nombre de su nueva función. De hecho, si miras la cadena de documentos para f
, estará en blanco porque with_logging
no tiene docstring, por lo que el docstring que escribiste ya no estará allí. Además, si observa el resultado de pydoc para esa función, no aparecerá en la lista como tomando un argumento x
; en su lugar, aparecerá como tomando *args
y **kwargs
porque eso es lo que lleva with_logging.
Si utilizar un decorador siempre significó perder esta información sobre una función, sería un problema grave. Por eso tenemos functools.wraps
. Esto toma una función utilizada en un decorador y agrega la funcionalidad de copiar el nombre de la función, la cadena de documentos, la lista de argumentos, etc. 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'
Muy a menudo uso clases, en lugar de funciones, para mis decoradores. Estaba teniendo problemas con esto porque un objeto no tendrá todos los mismos attributes que se esperan de una función. Por ejemplo, un objeto no tendrá la attribute __name__
. Tuve un problema específico con esto que fue bastante difícil de rastrear donde Django informaba el error “el objeto no tiene attribute ‘__name__
‘”. Desafortunadamente, para los decoradores de estilo de clase, no creo que @wrap haga el trabajo. En su lugar, he creado una clase de decorador base como esta:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
Esta clase representa a todos los attribute llama a la función que se está decorando. Entonces, ahora puede crear un decorador simple que verifique que 2 argumentos se especifiquen así:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
A partir de Python 3.5+:
@functools.wraps(f)
def g():
pass
Es un alias para g = functools.update_wrapper(g, f)
. Hace exactamente tres cosas:
- copia el
__module__
,__name__
,__qualname__
,__doc__
, y__annotations__
attributes def
sobreg
. Esta lista predeterminada está enWRAPPER_ASSIGNMENTS
, puedes verlo en la fuente de functools. - actualiza el
__dict__
deg
con todos los elementos def.__dict__
. (verWRAPPER_UPDATES
en la fuente) - establece un nuevo
__wrapped__=f
attribute sobreg
La consecuencia es que g
parece tener el mismo nombre, cadena de documentos, nombre de módulo y firma que f
. El único problema es que con respecto a la firma, esto no es realmente true: es sólo eso inspect.signature
sigue las cadenas de envoltura de forma predeterminada. Puede verificarlo usando inspect.signature(g, follow_wrapped=False)
como se explica en el doc. Esto tiene consecuencias molestas:
- el código contenedor se ejecutará incluso cuando los argumentos proporcionados no sean válidos.
- el código de envoltura no puede acceder fácilmente a un argumento usando su nombre, de los * args, ** kwargs recibidos. De hecho, uno tendría que manejar todos los casos (posicional, palabra clave, predeterminado) y, por lo tanto, usar algo como
Signature.bind()
.
Ahora hay un poco de confusión entre functools.wraps
y decoradores, porque un caso de uso muy frecuente para el desarrollo de decoradores es envolver 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 que preserva la firma para @wraps
. Tenga en cuenta que makefun
se basa en el mismo truco probado que el famoso decorator
Biblioteca.
Agradecemos que desees confirmar nuestra labor mostrando un comentario o dejando una puntuación te damos la bienvenida.