Декораторы Python и сохранение набора параметров декорированной функции (__signature__)

Проблема
Декораторы подменяют исходную функцию.
И при этом изменяет ее “сигнатуру” - набор параметров. У новой функции уже будет тот набор параметров, что в возвращенной декоратором функции, а не тот, что был у исходной.
Порой, это может создать проблемы. Например, если мы далее передаем эту функцию фреймворку swagger transmute, который автоматически строит swagger описание нашего API, анализируя наши обработчики запросов. Этот фреймворк описывает в swagger параметры запросов исходя из параметров наших функций- обработчиков этих запросов.
Но если мы обернули с какой-то целью наш обработчик например в такой декоратор:
def my_decorator(original_function):
def wrapper(*args, **kwargs):
try:
return original_function(*args, **kwargs)
finally:
some_clean_up()
return wrapperто фреймворк уже не увидит исходных параметров.
Он будет видеть только безликие *args, **kwargs.
И не построит нам корректного swagger-описания.
Как сохранить исходный набор параметров декорированной функции
Как описано в
PEP0362
в Python, начиная с 3.3, можно подменять сигнатуру функции с помощью
атрибута __signature__:
import inspect
def my_decorator(original_function):
def wrapper(*args, **kwargs):
try:
return original_function(*args, **kwargs)
finally:
some_clean_up()
wrapper.__signature__ = inspect.signature(original_function)
return wrapperКак сохранить прочие атрибуты функции после декорирования
Помимо этого, скорее всего мы также захотим сохранить еще ряд атрибутов функции.
Например, __doc__, что весьма немаловажно, как для автоматического создания
документации, так и для doc-tests - преставьте,
что иначе вы потеряете doc-тесты исходной функции.
Многие атрибуты исходной функции можно сохранить с помощью декоратора
wraps модуля functools:
import inspect
import functools
def my_decorator(original_function):
@functools.wraps
def wrapper(*args, **kwargs):
try:
return original_function(*args, **kwargs)
finally:
some_clean_up()
wrapper.__signature__ = inspect.signature(original_function)
return wrapperТолько __signature__ надо сохранять отдельно.
Это связано с тем что wraps сохраняеет существующие атрибуты функции, но
атрибута __signature__ скорее всего не будет в вашей исходной функции.
__signature__ не добавляется ко всем функциям автоматически, хотя
и корректно используется, если уже добавлено.
Поэтому этот атрибут и приходится получать с помощью inspect, а не копированием
из исходной функции с помощью wraps.