Предположим, я написал декоратор, который делает что-то очень общее. Например, он может преобразовывать все аргументы в определенный тип, вести журнал, реализовывать мемоизацию и т. Д.
Вот пример:
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 - Включите правильную подпись в строку документации:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
Это плохо из-за дублирования. Подпись по-прежнему не будет отображаться должным образом в автоматически созданной документации. Функцию легко обновить и забыть об изменении строки документации или сделать опечатку. [ И да, я знаю, что строка документации уже дублирует тело функции. Пожалуйста, проигнорируйте это; 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?
Любые другие подходы?
inspect.Signature
добавилось к украшенным функциям.