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, existe una solución alternativa:
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 - Incluya la firma correcta en la cadena de documentos:
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 de cometer un error tipográfico. [ Y sí, soy consciente del hecho de que la cadena de documentos ya duplica el cuerpo de la función. Por favor ignore 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 es inútil en general. Como decía al principio, quiero poder utilizar decoradores de forma totalmente genérica.
Estoy buscando una solución que sea completamente general y automática.
Entonces, la pregunta es: ¿hay alguna 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 ejecutivo?
¿Algún otro enfoque?
inspect.Signature
añadía al tratamiento de las funciones decoradas.Respuestas:
Instale el módulo decorador :
Adaptar la definición de
args_as_ints()
:Python 3.4+
functools.wraps()
de stdlib conserva las firmas desde Python 3.4:functools.wraps()
está disponible al menos desde Python 2.5 pero no conserva la firma allí:Aviso: en
*args, **kwargs
lugar dex, y, z=3
.fuente
functools.wraps()
ya conserva firmas en Python 3.4+ (como se dice en la respuesta). ¿Te refieres a la configuración dewrapper.__signature__
ayudas en versiones anteriores? (¿Qué versiones ha probado?)help()
muestra la firma correcta en Python 3.4. ¿Por qué crees quefunctools.wraps()
está roto y no IPython?help()
produce el resultado correcto, la pregunta es qué pieza de software debería arreglarse: ¿functools.wraps()
o IPython? En cualquier caso, la asignación manual__signature__
es una solución alternativa en el mejor de los casos, no es una solución a largo plazo.inspect.getfullargspec()
todavía no devuelve la firma adecuadafunctools.wraps
en Python 3.4 y que debe usarinspect.signature()
en su lugar.Esto se resuelve con la biblioteca estándar de Python
functools
y lafunctools.wraps
función específica , que está diseñada para " actualizar una función contenedora para que se parezca a la función empaquetada ". Sin embargo, su comportamiento depende de la versión de Python, como se muestra a continuación. Aplicado al ejemplo de la pregunta, el código se vería así:Cuando se ejecuta en Python 3, esto produciría lo siguiente:
Su único inconveniente es que en Python 2, sin embargo, no actualiza la lista de argumentos de la función. Cuando se ejecuta en Python 2, producirá:
fuente
Hay un módulo
decorator
decorador con decorador que puede utilizar:Luego se conserva la firma y la ayuda del método:
EDITAR: JF Sebastian señaló que no modifiqué la
args_as_ints
función, ahora está arreglada.fuente
Eche un vistazo al módulo decorador , específicamente el decorador decorador, que resuelve este problema.
fuente
Segunda opción:
$ easy_install envuelto
Wrap tiene una bonificación, conserva la firma de la clase.
fuente
Como se comentó anteriormente en la respuesta de jfs ; si le preocupa la firma en términos de apariencia (
help
, yinspect.signature
), entonces usarfunctools.wraps
está perfectamente bien.Si le preocupa la firma en términos de comportamiento (en particular
TypeError
en caso de que los argumentos no coincidan),functools.wraps
no la conserve. Debería usardecorator
para eso, o mi generalización de su motor central, llamadomakefun
.Vea también esta publicación sobre
functools.wraps
.fuente
inspect.getfullargspec
no se guarda llamandofunctools.wraps
.