python - ¿Qué hace functools.wraps?
python 3 reduce (4)
Requisito previo: Debe saber cómo usar decoradores y especialmente con envolturas. Este comment explica un poco claro o este link también lo explica bastante bien.
Siempre que usamos For eg: @wraps seguido de nuestra propia función de envoltura. Según los detalles dados en este link , dice que
functools.wraps es una función conveniente para invocar a update_wrapper () como un decorador de funciones, cuando se define una función de envoltura.
Es equivalente a parcial (update_wrapper, empaquetado = envuelto, asignado = asignado, actualizado = actualizado).
Entonces, @wraps decorator en realidad llama a functools.partial (func [, * args] [, ** keywords]).
La definición de functools.partial () dice que
El valor parcial () se utiliza para la aplicación de función parcial que "congela" parte de los argumentos y / o palabras clave de una función, lo que da como resultado un nuevo objeto con una firma simplificada. Por ejemplo, parcial () se puede usar para crear una llamada que se comporte como la función int () donde el argumento base se establece de manera predeterminada en dos:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = ''Convert base 2 string to an int.''
>>> basetwo(''10010'')
18
Lo que me lleva a la conclusión de que, @wraps hace una llamada a partial () y le pasa la función de contenedor como parámetro. La parcial () al final devuelve la versión simplificada, es decir, el objeto de lo que está dentro de la función de envoltura y no la función de envoltura en sí.
En un comentario sobre la respuesta a otra pregunta , alguien dijo que no estaba seguro de qué estaba haciendo functools.wraps. Entonces, hago esta pregunta para que haya un registro de ella en StackOverflow para futuras referencias: ¿qué hace exactamente functools.wraps?
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 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 ninguna cadena de documentos, por lo que la cadena de documentos que escribiste ya no estará allí. Además, si observa el resultado de pydoc para esa función, no aparecerá como un argumento x
; en lugar de eso, aparecerá como *args
y **kwargs
porque eso es lo que toma with_logging.
Si usar un decorador siempre significaba perder esta información sobre una función, sería un problema serio. Es por eso que 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, cadena de documentos, lista de argumentos, etc. Y dado que wraps
es 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''
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:
- wraps (f) devuelve un objeto, digamos O1 . Es un objeto de la clase parcial.
- El siguiente paso es @ O1 ... que es la notación de decorador en python. Significa
wrapper = O1 .__ call __ (wrapper)
Verificando la implementación de __call__ , vemos que después de este paso, (el lado izquierdo) la envoltura se convierte en el objeto resultante de selffunc (* self.args, * args, ** newkeywords) Verificando la creación de O1 en __new__ , saber self.func es la función update_wrapper . Utiliza el parámetro * args , la envoltura del lado derecho, como su primer parámetro. Al verificar el último paso de update_wrapper , se puede ver que se devuelve el envoltorio del lado derecho, con algunos de los atributos modificados según sea necesario.
Muy a menudo uso clases, en lugar de funciones, para mis decoradores. Estaba teniendo algunos problemas con esto porque un objeto no tendrá todos los 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 estaba informando el error "el objeto no tiene ningún atributo '' __name__
''". Desafortunadamente, para los decoradores de clase, no creo que @wrap haga el trabajo. En lugar de eso, he creado una clase de decorador de base así:
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 distribuye todos los atributos 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)