Python – Changing Large Number of If-Elif-Else Statements to Use Underlying Structure

clean codefunctionsif statementpython

I have a function that looks something like this:

function_name(step, ... , typ):
    if typ == 'some type of calc method':
         if step == 1:
             do_me_at_step_1(...)
         elif step == 2:
             do_me_at_step_2(...)
         elif ...
    elif typ == 'another calc method':
         if step == 1:
             do_me_at_step_1(...)
         elif ...

Hopefully the general structure is clear here. What are the differences between the different typs and how they are handled? There is one instance of typ which changes one variable in a very different way from all the others. All instances of typ vary in the number of separate step instructions, ranging from an if step == 1: ... else: to if step == 1 ... elif step == 5 ... else. The last elif and the else statements are different, basically.

How can I better structure this function, because it currently looks horrific?

My thought was to pull out all the initial steps…but I have to have a way to associate each typ with the step which becomes "key". So I thought somewhere along the lines of:

function_name(step, ... , typ):
    orders = {typ1:key_step, typ2:key_step, ...}
    while key_step['typ1'] != step:
        if step == 1:
            ...
        step +=1

But I noticed two problems: one, this doesn't really fully take advantage of the while loop. It would require me to add if statements up until the max step out of all the typs that is not "key" in there, which doesn't seem to me to really take advantage of the information in orders.

The other problem is once you are out of the while loop. I suppose you could do something like

if typ == 'something':
    do_the_key_step_function_for_this_typ(...)

but this really isn't doing anything new to clean up this function. How can I change the structure of this code so it is cleaner and shorter? There's an underlying structure, so I feel like there should be a better way to do this.

Feel free to ask me if you need any specifics about this function, but hopefully there are enough details provided here. Thanks!

Best Answer

Your initial function executes one specific step for one specific type, both of them being provided as argument. In the second case you iterate over steps trying to find the right one.

Alternative 1: double dictionary

Based on your alternative, I propose you to consider a dictionary of dictionaries, using two indexes instead of just one. Exemple:

# just some functions to be invoked
def do_me_at_step_1():
    return 1

def do_me_at_step_2():
    return 2

def do_other_step_2():
    return 102

# Use a dictionary assigning one type to a second level dictionary   
# In the second level dictionary, assign one step with a function 
command = { 'A': { 1:do_me_at_step_1, 2:do_me_at_step_2 }, 'B': { 1:do_me_at_step_1, 2:do_other_step_2 } }

# How you'd invoke the right function in function_name()
print (command['A'][1]())
print (command['A'][2]())
print (command['B'][1]())
print (command['B'][2]())

So in this case, your function_name() just finds the function to invoke using a dictionary of dictionaries, and then invokes them.

Alternative 2: object oriented code ?

You could also consider using an object oriented approach:

So in fact, you'd create different classes, each corresponding to a different type. And then you'd invoke for an instantiation of one specific class the method corresponding to the step.

In fact, the implementation would be very close to the the state pattern