Предположим, что я написал декоратор, который делает что-то очень общее. Например, он может преобразовывать все аргументы в определенный тип, выполнять ведение журнала, осуществлять memoization и т.д.
Вот пример:
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
Все хорошо. Однако есть одна проблема. Декорированная функция не сохраняет документацию исходной функции:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
К счастью, существует обходное решение:
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
На этот раз имя функции и документация верны:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
Но есть еще проблема: сигнатура функции неверна. Информация "* args, ** kwargs" находится рядом с бесполезной.
Что делать? Я могу думать о двух простых, но ошибочных обходных решениях:
1 - Включить правильную подпись в docstring:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
Это плохо из-за дублирования. Подпись не будет отображаться должным образом в автоматически созданной документации. Легко обновить функцию и забыть об изменении docstring или опечатке. [ И да, я знаю, что docstring уже дублирует тело функции. Пожалуйста, проигнорируйте это; funny_function - это просто случайный пример.]
2 - Не используйте декоратор или используйте специальный декоратор для каждой конкретной подписи:
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
Это отлично подходит для набора функций, которые имеют идентичную подпись, но в общем бесполезны. Как я уже сказал вначале, я хочу использовать декораторы полностью в целом.
Я ищу решение, которое является полностью общим и автоматическим.
Итак, вопрос в том, есть ли способ отредактировать декодированную подпись функции после ее создания?
В противном случае я могу написать декоратор, который извлекает подпись функции и использует эту информацию вместо "* kwargs, ** kwargs" при построении декорированной функции? Как извлечь эту информацию? Как мне создать декорированную функцию - с помощью exec?
Любые другие подходы?