Saltar al contenido

¿Qué hace functools.wraps?

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 de f sobre g. Esta lista predeterminada está en WRAPPER_ASSIGNMENTS, puedes verlo en la fuente de functools.
  • actualiza el __dict__ de g con todos los elementos de f.__dict__. (ver WRAPPER_UPDATES en la fuente)
  • establece un nuevo __wrapped__=f attribute sobre g

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *