Декораторы 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


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

Немного неудобно, что __signature__ надо сохранять отдельно.

К сожалению, вы не можете указать wraps сохранять также __signature__. Для wraps можно указывать произвольный список сохраняемых атрибутов, но __signature__ скорее всего не является атрибутом исходной функции. Потому что __signature__ не добавляется ко всем функциям автоматически, хотя и корректно используется, если уже добавлено. Поэтому этот атрибут и приходится получать с помощью inspect, а не копированием из исходной функции с помощью wraps.

Опубликовано November 16, 2018