Conservando firmas de funciones decoradas.

Supongamos que he escrito un decorador que hace algo muy genérico. Por ejemplo, podría convertir todos los argumentos a un tipo específico, realizar el registro, implementar la memorización, etc.

Aquí hay un ejemplo:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

>>> funny_function("3", 4.0, z="5")
22

Todo bien hasta ahora. Hay un problema, sin embargo. La función decorada no conserva la documentación de la función original:

>>> help(funny_function)
Help on function g in module __main__:

g(*args, **kwargs)

Afortunadamente, hay una solución:

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

Esta vez, el nombre de la función y la documentación son correctos:

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

Pero todavía hay un problema: la firma de la función es incorrecta. La información "* args, ** kwargs" es casi inútil.

¿Qué hacer? Puedo pensar en dos soluciones simples pero defectuosas:

1 - Incluir la firma correcta en la cadena de documentación:

def funny_function(x, y, z=3):
    """funny_function(x, y, z=3) -- computes x*y + 2*z"""
    return x*y + 2*z

Esto es malo debido a la duplicación. La firma aún no se mostrará correctamente en la documentación generada automáticamente. Es fácil actualizar la función y olvidarse de cambiar la cadena de documentos, o hacer un error tipográfico. ElY sí, soy consciente del hecho de que la cadena de documentación ya duplica el cuerpo de la función. Por favor ignora esto; funny_function es solo un ejemplo aleatorio.]

2 - No use un decorador, o use un decorador de propósito especial para cada firma específica:

def funny_functions_decorator(f):
    def g(x, y, z=3):
        return f(int(x), int(y), z=int(z))
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

Esto funciona bien para un conjunto de funciones que tienen una firma idéntica, pero en general es inútil. Como dije al principio, quiero poder usar decoradores de forma totalmente genérica.

Estoy buscando una solución que sea totalmente general y automática.

Entonces la pregunta es: ¿hay una manera de editar la firma de la función decorada después de que se haya creado?

De lo contrario, ¿puedo escribir un decorador que extraiga la firma de la función y use esa información en lugar de "* kwargs, ** kwargs" al construir la función decorada? ¿Cómo extraigo esa información? ¿Cómo debo construir la función decorada - con exec?

¿Algún otro enfoque?

Respuestas a la pregunta(6)

Su respuesta a la pregunta