Listing variables expected by a function in Python?
Asked Answered
S

3

13

I am wondering if it is possible to list the variables expected by a Python function, prior to calling it, in order to pass the expected variables from a bigger dict containing a lot of variables.

I have searched the net but couldn't find anything. However, the python interpreter can show the list of expected variables, so there surely must be some way to do it in a script?

Smoothbore answered 26/3, 2013 at 13:48 Comment(0)
B
21

You can use either the inspect.signature() or inspect.getfullargspec() functions:

import inspect

argspec = inspect.getfullargspec(somefunction)
signature = inspect.signature(somefunction)

inspect.fullargspec returns a named tuple with 7 elements:

  • A list with the argument names
  • The name of the catchall *args parameter, if defined (None otherwise)
  • The name of the catchall **kwargs parameter, if defined (None otherwise)
  • A tuple with default values for the keyword arguments; they go with the last elements of the arguments; match these by length of the default values tuple.
  • A list of keyword-only parameter names
  • A dictionary of default values for the keyword-only parameter names, if any
  • and a dictionary containing the annotations

With inspect.signature() you get a Signature object, a rich object that models not only the above data as a more structured set of objects but also lets you bind values to parameters the same way a call to the function would.

Which one is better will depend on your use cases.

Demo:

>>> import inspect
>>> def foo(bar, baz, spam='eggs', *monty, python: "kwonly", spanish=42, **inquisition) -> "return annotation":
...     pass
... 
>>> inspect.getfullargspec(foo)
FullArgSpec(args=['bar', 'baz', 'spam'], varargs='monty', varkw='inquisition', defaults=('eggs',), kwonlyargs=['python', 'spanish'], kwonlydefaults={'spanish': 42}, annotations={'return': 'return annotation', 'python': 'kwonly'})
>>> signature = inspect.signature(foo)
>>> signature
<Signature (bar, baz, spam='eggs', *monty, python: 'kwonly', spanish=42, **inquisition) -> 'return annotation'>
>>> signature.parameters['python'].kind.description
'keyword-only'
>>> signature.bind('Eric', 'Idle', 'John', python='Cleese')
<BoundArguments (bar='Eric', baz='Idle', spam='John', python='Cleese')>

If you have a dictionary named values of possible parameter values, I'd use inspect.signature() and use the Signature.parameters mapping to match names:

posargs = [
    values[param.name]
    for param in signature.parameters.values()
    if param.kind is Parameter.POSITIONAL_ONLY
]
skip_kinds = {Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD}
kwargs = {
    param.name: values[param.name]
    for param in signature.parameters.values()
    if param.name in values and param.kind not in skip_kinds
}

The above gives you a list of values for the positional-only parameters, and a dictionary for the rest (excepting any *args or **kwargs parameters).

Biocellate answered 26/3, 2013 at 13:49 Comment(0)
S
2

Just as a side answer, I now use another approach to pass to functions the variables they expect: I pass them all.

What I mean is that I maintain a kind of global/shared dictionnary of variables in my root object (which is the parent of all other objects), eg:

shareddict = {'A': 0, 'B':'somestring'}

Then I simply pass this dict to any method of any other object that is to be called, just like this:

shareddict.update(call_to_func(**shareddict))

As you can see, we unpack all the keys/values in shareddict as keyword arguments to call_to_func(). We also update shareddict with the returned result, we'll see below why.

Now with this technic, I can simply and clearly define in my functions/methods if I need one or several variables from this dict:

my_method1(A=None, *args, **kwargs):
''' This method only computes on A '''
    new_A = Do_some_stuff(A)
    return {'A': new_A} # Return the new A in a dictionary to update the shared value of A in the shareddict

my_method2(B=None, *args, **kwargs):
''' This method only computes on B '''
    new_B = Do_some_stuff(B)
    return {'B': new_B} # Return the new B in a dictionary to update the shareddict

my_method3(A=None, B=None, *args, **kwargs):
''' This method swaps A and B, and then create a new variable C '''
    return {'A': B, 'B': A, 'C': 'a_new_variable'} # Here we will update both A and B and create the new variable C

As you can notice, all the methods above return a dict of variables, which will update the shareddict, and which will get passed along to other functions.

This technic has several advantages:

  • Quite simple to implement
  • Elegant way to maintain a shared list of variables but without using a global variable
  • Functions and methods clearly show in their definitions what they expect (but of course one caveat is that even mandatory variables will need to be set as a keyword argument with a default value such as None, which usually means that the variable is optional, but here it's not
  • The methods are inheritable and overloadable
  • Low memory footprint since the same shareddict is passed all along
  • The children functions/methods define what they need (bottom-up), instead of the root defining what arguments will be passed to children (top-down)
  • Very easy to create/update variables
  • Optionally, it's VERY easy to dump all those variables in a file, eg by using json.dumps(finaldict, sort_keys=True).
Smoothbore answered 8/8, 2013 at 0:8 Comment(0)
L
1

Nice and easy:

import inspect  #library to import  
def foo(bar, baz, spam='eggs', *monty, **python): pass  #example function

argspec = inspect.signature(foo)
print(argspec) #print your output

prints: (bar, baz, spam='eggs', *monty, **python)

It also works for methods inside classes (very useful!):

class Complex: #example Class
     def __init__(self, realpart, imagpart): #method inside Class
...         self.r = realpart
...         self.i = imagpart

argspec = inspect.signature(Complex)
print(argspec)

prints: (realpart, imagpart)

Larkins answered 23/5, 2020 at 11:58 Comment(1)
Thank you for the heads up. inspect.signature() was introduced in Python 3.3, so it wasn't available at the time this question was made. But nowadays, this is likely the best solution, because it also returns the annotations/expected types of the arguments if available: docs.python.org/3/library/inspect.html - so for modern pythonistas, please consider this answer instead of the old accepted one.Smoothbore

© 2022 - 2024 — McMap. All rights reserved.