Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the argument spec on a decorated function?

Tags:

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)

like image 467
David Cramer Avatar asked Oct 19 '10 20:10

David Cramer


People also ask

Can decorator take argument?

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.

Can decorator take arguments Python?

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.

What is a decorated function Python?

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.

What are function decorators?

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.


2 Answers

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))
like image 172
unutbu Avatar answered Oct 03 '22 03:10

unutbu


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

Simply create a new decorator by subclassing

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)

Use it like any normal decorated function

args = inspect.getargspec(my_really_cool_function)
print args

my_really_cool_function(1,2,3,4,5)

Output

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.
like image 29
OozeMeister Avatar answered Oct 03 '22 03:10

OozeMeister