I need to determine the argspec (inspect.getargspec) of a function within a decorator:
def decor(func):
@wraps(func)
def _decor(*args, **kwargs):
return func(*args, **kwargs)
return _decor
@decor
def my_func(key=1, value=False):
pass
I need to be able to inspect the wrapped "my_func" and return the key/value arguments and their defaults. It seems that inspect.getargspec doesn't get the proper function.
(FWIW I need this for some runtime inspection/validation and later documentation generation)
The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.
Python decorator are the function that receive a function as an argument and return another function as return value. The assumption for a decorator is that we will pass a function as argument and the signature of the inner function in the decorator must match the function to decorate.
A decorator in Python is a function that takes another function as its argument, and returns yet another function . Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.
By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
If you use Michele Simionato's decorator module to decorate your function,
its decorator.decorator
will preserve the original function's signature.
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))
I've written a simple class that does what you want. This will achieve the same thing that functools.wraps
does as well as preserve the function's signature (from getargspec
's point of view). Read the docstring for this class on my gist for more information.
Note: this only works on decorating functions and not on class methods.
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's 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With