Suponha que eu tenha escrito um decorador que faz algo muito genérico. Por exemplo, ele pode converter todos os argumentos para um tipo específico, realizar registro, implementar memoização, etc.
Aqui está um exemplo:
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
Tudo bem até agora. No entanto, há um problema. A função decorada não retém a documentação da função original:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Felizmente, existe uma solução 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
Desta vez, o nome da função e a documentação estão corretos:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Mas ainda há um problema: a assinatura da função está errada. A informação "* args, ** kwargs" é quase inútil.
O que fazer? Posso pensar em duas soluções alternativas simples, mas falhas:
1 - Inclua a assinatura correta na docstring:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
Isso é ruim por causa da duplicação. A assinatura ainda não será mostrada corretamente na documentação gerada automaticamente. É fácil atualizar a função e esquecer de alterar a docstring ou cometer um erro de digitação. [ E sim, estou ciente do fato de que a docstring já duplica o corpo da função. Ignore isso; funny_function é apenas um exemplo aleatório. ]
2 - Não use um decorador, ou use um decorador especial para cada assinatura 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
Isso funciona bem para um conjunto de funções com assinatura idêntica, mas é inútil em geral. Como disse no início, quero poder usar decoradores de forma totalmente genérica.
Estou procurando uma solução totalmente geral e automática.
Portanto, a questão é: há uma maneira de editar a assinatura da função decorada depois que ela foi criada?
Caso contrário, posso escrever um decorador que extraia a assinatura da função e use essa informação em vez de "* kwargs, ** kwargs" ao construir a função decorada? Como faço para extrair essas informações? Como devo construir a função decorada - com exec?
Alguma outra abordagem?
inspect.Signature
para lidar com funções decoradas.