Category Archives: Python

Python tip: Inspect function signature at runtime

Problem:

I have a list of functions with different signatures. There is some set of possible parameters, and I want to call all these functions with the “appropriate” argument for each parameter.

This is a little hand-wavy, let’s look at an example:


def half(a):
    return a / 2

def twice(a):
    return 2 * a

def addition(a, b):
    return a + b

def subtraction(a, b):
    return a - b

functions = [half, twice, addition, subtraction]
a = get_a()
b = get_b()

Desired outcome:

[half(a), twice(a), addition(a,b), subtraction(a, b)]

And we want to do this without making our function definitions too ugly.

Solution 1:

One option is to `get_b()` within the functions that need them. This is not ideal, suppose `get_b` is not a pure function (e.g. a network call), we would want to pass `b` into scope instead of getting it from elsewhere every time it’s needed.

Solution 2:

We could change the signature to accept arbitrary kwargs and then pass a dict of args, for example:

def half(**kwargs):
    return kwargs['a'] / 2

def twice(**kwargs):
    return 2 * kwargs['a']

def addition(**kwargs):
    return kwargs['a'] + kwargs['b']

def subtraction(**kwargs):
    return kwargs['a'] - kwargs['b']

functions = [half, twice, addition, subtraction]
payload = {'a': get_a(), 'b': get_b()}
results = [f(**payload) for f in functions]

This works, but makes each of our function definitions uglier.

Solution 3:

Allow each function to have a different signature, inspect the signature at runtime and pass what is needed.
(Adapted from http://stackoverflow.com/a/2677263/3393459)


import inspect

def half(a):
    return a / 2

def twice(a):
    return 2 * a

def addition(a, b):
    return a + b

def subtraction(a, b):
    return a - b

# Wrapper which:
# * accepts a dict of all possible kwargs and their names
# * inspects the signature of the function
# * calls that function with the correct args
def call_func_with_correct_args(f, possible_args):
    func_args = inspect.getargspec(f).args
    args_to_pass = {k: possible_args[k] for k in func_args}
    return f(**args_to_pass)

functions = [half, twice, addition, subtraction]
a = get_a()
b = get_b()
full_payload = {'a': a, 'b': b}

results = [call_func_with_correct_args(f, full_payload) for f in functions]

So this is nice and clever, but we need to be careful that our function parameters are named correctly and consistently. Essentially we are passing the burden to the function definitions.

Conclusion

I don’t know what a great solution to this might look like. Is there a better way to do this? If all the parameters are different types, Python3’s type hinting might provide another option. What does this look like in other languages?