Getting arguments for any callable object?

5.3k Views Asked by At

I am interested in obtaining a general way to obtain a list of arguments and keyword arguments taken by a callable Python object. This is straight-forward for functions with the inspect.getargspec function, for example:

import inspect
from functools import partial

def foo(*args, **kwargs):
    return args, kwargs

print(inspect.getargspec(foo))
>>ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)

def bar(*args):
    return args

print(inspect.getargspec(bar))
>>ArgSpec(args=[], varargs='args', keywords=None, defaults=None)

However, this fails in cases such as these:

partial_function = partial(foo, kwarg="value")

inspect.getargspec(partial_function)
>>TypeError: <functools.partial object at 0x11748bc58> is not a Python function

class Foo(object):
    def __call__(self, *args, **kwargs):
        return args, kwargs

foo_instance = Foo()

inspect.getargspec(foo_instance)
>>TypeError: <__main__.Foo object at 0x116c13ed0> is not a Python function

inspect.getargspec(zip)
>>TypeError: <built-in function zip> is not a Python function

Note, that there are ways to obtain the arguments for the partial function and for the callable object, i.e:

inspect.getargspec(partial_function.func)
>>ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)

inspect.getargspec(foo_instance.__call__)
>>ArgSpec(args=['self'], varargs='args', keywords='kwargs', defaults=None)

but I would like a more general way of obtaining these results, if at all possible.

2

There are 2 best solutions below

0
On

getfullargspec also handles partials correctly. It was previously deprecated but was undeprecated since it was deemed that it is quite useful for single-source Python 2/3 polyglot code.

Another option is signature which is a bit more complicated.

0
On

All these can be handled by using the inspect.signature helper function which, as stated in its docs:

Accepts a wide range of python callables, from plain functions and classes to functools.partial() objects.

What signature does is it takes your callable and constructs a Signature object from it:

>>> from inspect import signature
>>> s = signature(Foo())  # class as shown in your example

with the arguments now lying in the parameters mapping attribute for the Signature instance:

>>> s.parameters
mappingproxy({'args': <Parameter "*args">, 'kwargs': <Parameter "**kwargs">})

With a partial object, you'd get the corresponding representation:

>>> def foo(a, b, c): pass
>>> p = partial(foo, c = 30)
>>> signature(p).parameters
<Signature (a, b, *, c=20)>

As for built-in functions such as zip, this isn't always possible, some of these functions don't expose the appropriate metadata to construct a signature, from PEP 362:

Some functions may not be introspectable in certain implementations of Python. For example, in CPython, built-in functions defined in C provide no metadata about their arguments. Adding support for them is out of scope for this PEP.

So, while zip isn't the type to expose information about itself, others are, e.g all:

>>> signature(all)
<Signature (iterable, /)>

you'll unfortunately require a try-except for these, there's nothing else that can be done.

The cases that the signature helper handles are enumerated in the implementation section of PEP 362 which introduced it.

In general, inspect.getargspec has been deprecated for a while now in Python 3, the suggested approach (if you're using Python 3, that is) is to use the representation offered via Signature objects.

If on Python 2, I'm pretty sure you can't do it directly with getargspec, you should use getfullargspec as Anttis' answer suggests.