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.
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': {}}