Как я могу получить спецификацию аргумента на украшенной функции?
Мне нужно определить argspec (inspect.getargspec) функции внутри декоратора:
def decor(func):
@wraps(func)
def _decor(*args, **kwargs):
return func(*args, **kwargs)
return _decor
@decor
def my_func(key=1, value=False):
pass
Мне нужно проверить обернутый "my_func" и вернуть аргументы ключ/значение и их значения по умолчанию. Кажется, что inspect.getargspec не получает правильную функцию.
(FWIW Мне нужно это для некоторой проверки/проверки времени выполнения и последующей генерации документации)
Ответы
Ответ 1
Если вы используете Michele Simionato decorator module, чтобы украсить вашу функцию,
его decorator.decorator
сохранит исходную сигнатуру функции.
import inspect
import decorator
@decorator.decorator
def decor(my_func,*args,**kw):
result=my_func(*args,**kw)
return result
@decor
def my_func(key=1, value=False):
pass
decorated_argspec = inspect.getargspec(my_func)
print(decorated_argspec)
# ArgSpec(args=['key', 'value'], varargs=None, keywords=None, defaults=(1, False))
Ответ 2
Я написал простой класс, который делает то, что вы хотите. Это позволит достичь того же, что и functools.wraps
, а также сохранить подпись функции (с точки зрения getargspec
). Прочтите docstring для этого класса мой смысл для получения дополнительной информации.
Примечание: это работает только на функции украшения, а не на методах класса.
import types
class decorator(object):
def __getattribute__(self, name):
if name == '__class__':
# calling type(decorator()) will return <type 'function'>
# this is used to trick the inspect module >:)
return types.FunctionType
return super(decorator, self).__getattribute__(name)
def __init__(self, fn):
# let pretend for just a second that this class
# is actually a function. Explicity copying the attributes
# allows for stacked decorators.
self.__call__ = fn.__call__
self.__closure__ = fn.__closure__
self.__code__ = fn.__code__
self.__doc__ = fn.__doc__
self.__name__ = fn.__name__
self.__defaults__ = fn.__defaults__
self.func_defaults = fn.func_defaults
self.func_closure = fn.func_closure
self.func_code = fn.func_code
self.func_dict = fn.func_dict
self.func_doc = fn.func_doc
self.func_globals = fn.func_globals
self.func_name = fn.func_name
# any attributes that need to be added should be added
# *after* converting the class to a function
self.args = None
self.kwargs = None
self.result = None
self.function = fn
def __call__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.before_call()
self.result = self.function(*args, **kwargs)
self.after_call()
return self.result
def before_call(self):
pass
def after_call(self):
pass
Просто создайте новый декоратор путем подкласса
import time
class timeit(decorator):
def before_call(self):
self.start = time.time()
def after_call(self):
end = time.time()
print "Function {0} took {1} seconds to complete.".format(
self.__name__, end - self.start
)
@timeit
def my_really_cool_function(a, b, c, d='asdf', q='werty'):
time.sleep(5)
Используйте его, как любая нормальная декорированная функция
args = inspect.getargspec(my_really_cool_function)
print args
my_really_cool_function(1,2,3,4,5)
Выход
ArgSpec(args=['a', 'b', 'c', 'd', 'q'], varargs=None,
keywords=None, defaults=('asdf', 'werty'))
Function my_really_cool_function took 5.0 seconds to complete.