Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Listing variables expected by a function in Python?

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?

like image 992
gaborous Avatar asked Mar 26 '13 13:03

gaborous


People also ask

How do you find the list of arguments in a function in Python?

To extract the number and names of the arguments from a function or function[something] to return ("arg1", "arg2"), we use the inspect module. The given code is written as follows using inspect module to find the parameters inside the functions a Method and foo.

Can you pass a list as an argument in a function?

You can send any data types of argument to a function (string, number, list, dictionary etc.), and it will be treated as the same data type inside the function.

How do you retrieve data from a function in Python?

You can put a return statement at the end of a function to return variables ( readvar in this case) (and you almost always should). Then you can assign the returned argument ( readvar ) to a new variable (e.g. rv ). You can also give it the same name.

How do you pass variable number of arguments to a function in Python?

To pass a variable number of arguments to a function in Python, use the special syntax *args in the function specification. It is used to pass a variable-length, keyword-free argument list.


3 Answers

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).

like image 197
Martijn Pieters Avatar answered Nov 08 '22 03:11

Martijn Pieters


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).
like image 37
gaborous Avatar answered Nov 08 '22 05:11

gaborous


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)

like image 24
cristiandatum Avatar answered Nov 08 '22 05:11

cristiandatum