Search code examples
python-3.xfunctiondictionaryerror-handlingargs

Is it possible to adapt this approach of using dictionaries to find/evaluate one of many functions and its corresponding args?


Suppose one has several separate functions to evaluate some given data. Rather than use redundant if/else loops, one decides to use a dictionary key to find the particular function and its corresponding args. I feel like this is possible, but I can't figure out how to make this work. As a simplified example (that I hope to adapt for my case), consider the code below:

def func_one(x, a, b, c=0):
    """ arbitrary function """
    # c is initialized since it is necessary in func_two and has no effect in func_one
    return a*x + b

def func_two(x, a, b, c):
    """ arbitrary function """
    return a*x**2 + b*x + c

def pick_function(key, x=5):
    """ picks and evaluates arbitrary function by key """
    if key != (1 or 2):
        raise ValueError("key = 1 or 2")

    ## args = a, b, c
    args_one = (1, 2, 3)
    args_two = (4, 5, 3)

    ## function dictionary
    func_dict = dict(zip([1, 2], [func_one, func_two]))

    ## args dictionary
    args_dict = dict(zip([1, 2], [args_one, args_two]))

    ## apply function to args
    func = func_dict[key]
    args = args_dict[key]

    ## my original attempt >> return func(x, args)
    return func(x, *args) ## << EDITED SOLUTION VIA COMMENTS BELOW

print(func_one(x=5, a=1, b=2, c=3)) # prints 7

But,

print(pick_function(1)) 

returns an error message

  File "stack_overflow_example_question.py", line 17, in pick_function
    return func(x, args)
TypeError: func_one() missing 1 required positional argument: 'b'

Clearly, not all of the args are being passed through with the dictionary. I've tried various combinations of adding/removing extra brackets and paranthesis from args_one and args_two (as defined in pick_function). Is this approach fruitful? Are there other convenient (in terms of readability and speed) approaches that do not require many if/else loops?


Solution

  • To fix your code with minimal changes, change return func(x, args) to return func(x, *args). I think this is what Anton vBR is suggesting in the comments.


    However, I think your code could be further simplified by using the * ("splat") and ** ("double-splat"?) positional/keyword argument unpacking operators like this:

    def func_one(x, a, b, c=0):
        """ arbitrary function """
        # c is initialized since it is necessary in func_two and has no effect in func_one
        return a*x + b
    
    def func_two(x, a, b, c):
        """ arbitrary function """
        return a*x**2 + b*x + c
    
    def func(key, *args, **kwargs):
        funcmap = {1: func_one, 2: func_two}
        return funcmap[key](*args, **kwargs)
    
    def pick_function(key, x=5):
        """ picks and evaluates arbitrary function by key """
        argmap = {1: (1, 2, 3), 2: (4, 5, 3)}
        return func(key, x, *argmap[key])
    
    print(func_one(x=5, a=1, b=2, c=3)) 
    # 7
    print(pick_function(1)) 
    # 7
    print(func(1, 5, 1, 2, 3)) 
    # 7
    print(func(1, b=2, a=1, c=3, x=5)) 
    # 7