Vasu
Vasu

Reputation: 1110

How do I call a function with arbitrary parameters from another function?

I'd like to know how I would go about doing something like this (in python 2):

def myFunction(fn, params):
    return ("You called " + fn.__name__ + "(" + str(params) ")" + 
        " and got " + str(fn(params)))

Say I have a couple functions:

def doSomething(s):
    # manipulation
    return s

def doAnotherThing(someInt, someOtherInt, someString):
    # do something with them
    return someValue

I'd like to be able to call myFunction(doSomething, "hello"), and get You called doSomething("hello") and got "something". This approach seems to work for functions that take a single input, but I can't get it to work for functions that take multiple inputs, e.g. calling myFunction(doAnotherThing, (myInt, myOtherInt, myString)). I think I need to do something involving * and ** so that I can take the keywords arbitrarily instead of as a tuple, but I'm not quite sure how to go about doing that.

Upvotes: 0

Views: 109

Answers (2)

Roger Fan
Roger Fan

Reputation: 5045

You're close, all you need to do is add a * in front of the tuple (or ** in front of a dict for keyword arguments) when calling the interior function. This is called argument unpacking.

def wrapper(fn, params, keyword_params):
    return fn(*params, **keyword_params)

def myfunc(a, b, c=0):
    return (a + b)/c

wrapper(myfunc, (2, 5), {'c': 3})

You can also use arbitrary argument lists to potentially simplify the wrapper function. This will allow you to automatically package additional arguments to feed to interior functions without having to pre-package them in tuples and dictionaries.

def wrapper(fn, *params, **keyword_params):  # Note the asterisks
    return fn(*params, **keyword_params)

def myfunc(a, b, c=1):
    return (a + b)/c

wrapper(myfunc, 2, 5, c=3)  # Nicer function call

Note that just using the first method will actually break single-argument implementations, as the * operator expects an iterator to unpack. So you have to either always pre-package the arguments or add some type-checking in the wrapper function.

def wrapper(fn, params):
    return fn(*params)

def myfunc(a):
    return 2*a

wrapper(myfunc, 2)
# TypeError: myfunc() argument after * must be a sequence, not int

wrapper(myfunc, (2,))  # Package the argument in a single-element tuple
# 4

The second method doesn't have this problem.

Upvotes: 3

Robᵩ
Robᵩ

Reputation: 168596

Here is one way:

def myFunction(fn, *params):
    return ("You called " + fn.__name__ + str(params) +
        " and got " + str(fn(*params)))

import math
print myFunction(math.sqrt, 4)

print myFunction(open, '/etc/passwd', 'r')
print myFunction(lambda x: x+1, 41)

Result:

You called sqrt(4,) and got 2.0
You called open('/etc/passwd', 'r') and got <open file '/etc/passwd', mode 'r' at 0x7f20e9cb65d0>
You called <lambda>(41,) and got 42

Upvotes: 0

Related Questions