Ruben9922
Ruben9922

Reputation: 785

Calling functions with parameters using a dictionary in Python

I'm making a program which has a main menu that asks the user to input an option and store it in integer option1, which is looked up in dictionary options. The corresponding function is then run. The following code works if the functions have no parameters:

options = {0 : FunctionZero,    # Assign functions to the dictionary
            1 : FunctionOne,
            2 : FunctionTwo,
            3 : FunctionThree}

options[option1]()    # Call the function

If the functions have parameters the above code doesn't work as the () part assumes the functions have no parameters, but I tried the following, which stores the functions' names and parameters in tuples within the dictionary:

options = {0 : (FunctionZero,""),    # FunctionsZero, FunctionOne
            1 : (FunctionOne,""),    # and FunctionTwo have no parameters
            2 : (FunctionTwo,""),
            3 : (FunctionThree,True)}    # FunctionThree has one parameter

if options[option1][1] == "":    # Call the function
    options[option1][0]()
else:
    options[option1][0](options[option1][1])

This code seems to work fine, but I was wondering if there's a better way to do this, especially if the functions require several parameters? In other languages like C# I'd probably use a switch or case statement (which is not in Python) and I'm avoiding using if...elif statements for this.

Upvotes: 15

Views: 28165

Answers (3)

jonrsharpe
jonrsharpe

Reputation: 121976

I would do this using functools.partial to specify the arguments when the dictionary is created:

from functools import partial

options = {0: FunctionZero,   
           1: FunctionOne,    
           2: FunctionTwo,
           3: partial(FunctionThree, True)} 

Note that this also allows additional parameters to be passed when the function is called (as long as all the functions in the dictionary have the same parameters missing after partial has been called):

def test(one, two, three=None, four=None):
    ...

def test2(one, two, three=None):
    ...

options = {1: partial(test, 1, three=3, four=4),
           2: partial(test2, 1, three=3)}

...

options[choice](2) # pass the 'two' argument both functions still require

Upvotes: 23

Xirion11
Xirion11

Reputation: 65

To add to icktoofay's answer, if you want to pass an argument to the lambda just do the following:

def printDouble( number ):
    print number * 2

options = {
    1: lambda num: printDouble(num)
}

options[1](4)    #this prints 8

By adding the parameter for lambda before the ":" you state that the lambda receives a parameter and it is used then in the function it calls.

Also if you don't want to use lambdas you can use the usual way

def printDouble( num ):
    print num * 2

def printHalf( num ):
    print half / 2

functionDictionary = {
    'Double': printDouble,
    'Half'  : printHalf
}

functionDictionary['Double'](2)    #This prints 4

Upvotes: 4

icktoofay
icktoofay

Reputation: 129001

Sure. In Python, functions can take positional or keyword arguments. For most functions, arguments can be passed in either way, but that’s not necessarily the case for all functions, so we do need to keep them separate. Positional arguments are in an iterable (often list or tuple), and keyword arguments are in a dictionary from strings to values.

We could then represent each function as a tuple of function, positional arguments, and keyword arguments:

options = {
    0: (function_zero, [], {}),
    1: (function_one, [], {}),
    2: (function_two, [], {}),
    3: (function_three, [True], {}),
    4: (function_four, [], {'kwarg': True}),  # takes a keyword argument
}

Then you could call them like this:

func, args, kwargs = options[option1]
func(*args, **kwargs)

But if you’re always going to just pass in a constant, there’s a better way: just create little no-argument wrappers for each function that call the function how you want it to be called:

options = {
    0: function_zero,
    1: function_one,
    2: function_two,
    3: lambda: function_three(True),
    4: lambda: function_four(kwarg=True),
}

Then use your first method:

options[option1]()

As detailed in jonrsharpe’s answer, you can also use functools.partial rather than a lambda. As he notes, this has the advantage of being able to append some of your own arguments:

options[option1]('hello')  # adds 'hello' to previously-specified arguments

If you don’t need this functionality, though, a zero-parameter lambda will serve you just fine.

Upvotes: 9

Related Questions