Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine the arguments and keyword arguments of a function

How you determine the form of a valid call to a function?

For example, say we have a function info that accomplishes this; info might work like this (I'm open to suggestions on whatever might be a more complete and more consistent way to represent the information returned):

def foo():
    pass

info(foo)
# { 'args': (), 'kwargs': {} }

def bar(a):
    pass

info(bar)
# { 'args': ('a',), 'kwargs': {} }

def baz(a, b=42):
    pass

info(baz)
# { 'args': ('a',), 'kwargs': { 'b': 42 } }

def qux(a, *args, b=42, **kwargs):
    pass

info(qux)
# { 'args': ('a',), 'kwargs': { 'b': 42 }, 'optional': {'*args', '**kwargs'} }

The info function should work for any function. I am not sure how to write an example return for every pattern: For example, help(range.__init__) displays

# __init__(self, /, *args`, **kwargs)

and I am not sure what the / means.

The return from info needs to be something that be computed on (with reasonable effort) for the production of arbitrary, correct calls to info's argument, e.g., for randomized testing.

like image 861
Ana Nimbus Avatar asked Nov 16 '25 19:11

Ana Nimbus


1 Answers

There is already a function for this purpose, inspect.getfullargspec which returns namedtuples:

>>> import inspect
>>> inspect.getfullargspec(foo)
FullArgSpec(args=[], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
>>> inspect.getfullargspec(qux)
FullArgSpec(args=['a'], varargs='args', varkw='kwargs', defaults=None, kwonlyargs=['b'], kwonlydefaults={'b': 42}, annotations={})
>>> inspect.getfullargspec(bar)
FullArgSpec(args=['a'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
>>> inspect.getfullargspec(baz)
FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(42,), kwonlyargs=[], kwonlydefaults=None, annotations={})

However, if you want, you can build a function from this:

def info(func):
    """returns function argument info"""
    specs = inspect.getfullargspec(func)
    dict_ = {}
    dict_['args'] = tuple(specs.args)
    dict_['kwargs'] = {} if specs.kwonlydefaults is None else specs.kwonlydefaults
    dict_['optional'] = set()
    dict_['defaults'] = {} if specs.defaults is None else specs.defaults
    if specs.varargs is not None:
        dict_['optional'].add(f"*{specs.varargs}")
    if specs.varkw is not None:
        dict_['optional'].add(f"*{specs.varkw}")
    if not dict_['optional']:
        dict_['optional'] = {}
    return dict_

>>> info(foo)
{'args': (), 'kwargs': {}, 'optional': {}, 'defaults': {}}

>>> info(qux)
{'args': ('a',), 'kwargs': {'b': 42}, 'optional': {'*args', '*kwargs'}, 'defaults': {}}

>>> info(bar)
{'args': ('a',), 'kwargs': {}, 'optional': {}, 'defaults': {}}

>> info(baz)
{'args': ('a', 'b'), 'kwargs': {}, 'optional': {}, 'defaults': (42,)}

The 42 in baz is not a keyword argument, it is a default one. Because while calling it is not necessary to provide the keyword b.

The * in the help(__init__) refers to keyword only parameters to follow, i.e. it tells the following arguments must be keyword-only arguments, and similarly any argument preceding / has to be positional argument, for more see PEP457 , PEP570, PEP3102.

Many of these information can be obtained form the inherent code object of the function, which has following attributes:

for attr in dir(qux.__code__):
    if not attr.startswith('_'):
        print(attr,':',getattr(qux.__code__, attr))

co_argcount : 1
co_cellvars : ()
co_code : b'd\x00S\x00'
co_consts : (None,)
co_filename : <ipython-input-43-6608913c4d65>
co_firstlineno : 1
co_flags : 79
co_freevars : ()
co_kwonlyargcount : 1
co_lnotab : b'\x00\x01'
co_name : qux
co_names : ()
co_nlocals : 4
co_stacksize : 1
co_varnames : ('a', 'b', 'args', 'kwargs')

However, these are not descriptive enough, nor easy to access and intended for internal use for python. Hence unless you absolutely need a custom function, inspect.getfullargspec is probably the best option.

Output of fullargspec being a namedtuple you can access different fields easily:

>>> argspecs = inspect.getfullargspec(qux)
>>> argspecs.args
['a']
>>> argspecs.kwonlydefaults
{'b': 42}

And if you want a dict you can call the _asdict method of the resulting namedtuple:

>>> inspect.getfullargspec(qux)._asdict()  #gives OrderedDict
OrderedDict([('args', ['a']),
             ('varargs', 'args'),
             ('varkw', 'kwargs'),
             ('defaults', None),
             ('kwonlyargs', ['b']),
             ('kwonlydefaults', {'b': 42}),
             ('annotations', {})])
>>> dict(inspect.getfullargspec(qux)._asdict()) #call dict to get regular dict
{'args': ['a'],
 'varargs': 'args',
 'varkw': 'kwargs',
 'defaults': None,
 'kwonlyargs': ['b'],
 'kwonlydefaults': {'b': 42},
 'annotations': {}}
like image 181
Sayandip Dutta Avatar answered Nov 18 '25 11:11

Sayandip Dutta



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!