Предположим, я написал декоратор, который делает что-то очень общее. Например, он может преобразовывать все аргументы в определенный тип, вести журнал, реализовывать мемоизацию и т. Д.
Вот пример:
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
добавилось к украшенным функциям.Ответы:
Установите модуль декоратора :
Адаптировать определение
args_as_ints()
:Python 3.4+
functools.wraps()
from stdlib сохраняет подписи начиная с Python 3.4:functools.wraps()
доступен как минимум с Python 2.5, но не сохраняет там подпись:Примечание:
*args, **kwargs
вместоx, y, z=3
.источник
functools.wraps()
уже сохраняет подписи в Python 3.4+ (как сказано в ответе). Вы имеете в виду, что настройкаwrapper.__signature__
помогает в более ранних версиях? (какие версии вы тестировали?)help()
показывает правильную подпись на Python 3.4. Как вы думаете, почемуfunctools.wraps()
не работает IPython?help()
дает правильный результат, вопрос в том, какое программное обеспечение следует исправить:functools.wraps()
или IPython? В любом случае назначение вручную__signature__
- это в лучшем случае обходной путь, а не долгосрочное решение.inspect.getfullargspec()
все еще не возвращает правильную подписьfunctools.wraps
в python 3.4, которую вы должны использоватьinspect.signature()
вместо этого.Это решается с помощью стандартной библиотеки Python
functools
и, в частности,functools.wraps
функции, которая предназначена для « обновления функции-оболочки, чтобы она выглядела как функция-оболочка ». Однако его поведение зависит от версии Python, как показано ниже. Применительно к примеру из вопроса код будет выглядеть так:При выполнении в Python 3 это приведет к следующему:
Его единственный недостаток заключается в том, что в Python 2 список аргументов функции не обновляется. При выполнении в Python 2 он выдаст:
источник
Есть модуль
decorator
декоратора с декоратором, который вы можете использовать:Тогда подпись и справка метода сохраняются:
РЕДАКТИРОВАТЬ: JF Себастьян указал, что я не изменял
args_as_ints
функцию - теперь она исправлена.источник
Взгляните на модуль декоратора - в частности, декоратор- декоратор, который решает эту проблему.
источник
Второй вариант:
$ easy_install в оболочке
wrapt имеет бонус, сохраняет подпись класса.
источник
Как прокомментировано выше в ответе jfs ; если вас беспокоит внешний вид подписи (
help
, иinspect.signature
), то используйтеfunctools.wraps
вполне подойдет.Если вас беспокоит сигнатура с точки зрения поведения (в частности,
TypeError
в случае несоответствия аргументов),functools.wraps
не сохраняет ее. Вам лучше использоватьdecorator
для этого или мое обобщение его основного движка с именемmakefun
.См. Также этот пост о
functools.wraps
.источник
inspect.getfullargspec
не сохраняется результат по вызовуfunctools.wraps
.