Сохранение подписей украшенных функций

Предположим, я написал декоратор, который делает что-то очень общее. Например, он может преобразовывать все аргументы в определенный тип, выполнять ведение журнала, осуществлять мемонирование и т. Д.

Вот пример:

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?

Любые другие подходы?

5 Solutions collect form web for “Сохранение подписей украшенных функций”

  1. Установите модуль декоратора :

     $ pip install decorator 
  2. args_as_ints() определение args_as_ints() :

     import decorator @decorator.decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print funny_function("3", 4.0, z="5") # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z 

Python 3.4+

functools.wraps() из stdlib сохраняет подписи с Python 3.4:

 import functools def args_as_ints(func): @functools.wraps(func) def wrapper(*args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return func(*args, **kwargs) return wrapper @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print(funny_function("3", 4.0, z="5")) # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z 

functools.wraps() доступен, по крайней мере, с Python 2.5, но там не сохраняется подпись:

 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(*args, **kwargs) # Computes x*y + 2*z 

Обратите внимание: *args, **kwargs вместо x, y, z=3 .

Существует декораторский модуль с decorator декоратором, который вы можете использовать:

 @decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) 

Затем подпись и помощь метода сохраняются:

 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

EDIT: JF Себастьян отметил, что я не args_as_ints функцию args_as_ints – теперь она исправлена.

Посмотрите на модуль декоратора – в частности, декоратор- декоратор, который решает эту проблему.

Это решается с помощью стандартных библиотек Python и особенно функцией functools.wraps , которая предназначена для « обновления функции-обертки, чтобы выглядеть как завернутая функция ». Однако это зависит от версии Python, как показано ниже. Применив пример к вопросу, код будет выглядеть так:

 from functools import wraps def args_as_ints(f): @wraps(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 

При выполнении в Python 3 это приведет к следующему:

 >>> funny_function("3", 4.0, z="5") 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

Его единственным недостатком является то, что в Python 2 он не обновляет список аргументов функции. Когда выполняется в Python 2, он будет производить:

 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(*args, **kwargs) Computes x*y + 2*z 

Второй вариант:

  1. Установить модуль wrapt:

$ easy_install wrapt

wrapt имеют бонус, сохраняют подпись класса.

 import wrapt import inspect @wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z 

import wrapt import inspect @wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z

  • Применение одного декоратора к нескольким функциям
  • В чем разница между использованием декораторов и расширением подкласса по наследству?
  • Применение декораторов python к методам в классе
  • Почему декораторы Python вместо закрытия?
  • Как я могу назвать супер в декораторе метода в Python 3?
  • Как проверить декораторы функции Python?
  • Внутри класса декоратора, экземпляр доступа класса, который содержит декорированный метод
  • Как применить декоратор к каждому виду фляжки
  • Python - лучший язык программирования в мире.