Too many if statements
Asked Answered
W

7

14

I have some topic to discuss. I have a fragment of code with 24 ifs/elifs. Operation is my own class that represents functionality similar to Enum.
Here is a fragment of code:

if operation == Operation.START:
    strategy = strategy_objects.StartObject()
elif operation == Operation.STOP:
    strategy = strategy_objects.StopObject()
elif operation == Operation.STATUS:
    strategy = strategy_objects.StatusObject()
(...)

I have concerns from readability point of view. Is is better to change it into 24 classes and use polymorphism? I am not convinced that it will make my code maintainable... From one hand those ifs are pretty clear and it shouldn't be hard to follow, on the other hand there are too many ifs.

My question is rather general, however I'm writing code in Python so I cannot use constructions like switch.

What do you think?


UPDATE:

One important thing is that StartObject(), StopObject() and StatusObject() are constructors and I wanted to assign an object to strategy reference.

Wedge answered 31/7, 2015 at 14:27 Comment(2)
case statement, instead? that or have an array of method references to call dynamically, e.g. whatever python's equivalent of array_of_methods[operation]() would be...Gonfanon
Marc, python does not have switch/case statements. python.org/dev/peps/pep-3103Economize
B
20

You could possibly use a dictionary. Dictionaries store references, which means functions are perfectly viable to use, like so:

operationFuncs = {
    Operation.START: strategy_objects.StartObject
    Operation.STOP: strategy_objects.StopObject
    Operation.STATUS: strategy_objects.StatusObject
    (...)                  
}

It's good to have a default operation just in case, so when you run it use a try except and handle the exception (ie. the equivalent of your else clause)

try:
    strategy = operationFuncs[operation]()
except KeyError:
    strategy = strategy_objects.DefaultObject()

Alternatively use a dictionary's get method, which allows you to specify a default if the key you provide isn't found.

strategy = operationFuncs.get(operation(), DefaultObject())

Note that you don't include the parentheses when storing them in the dictionary, you just use them when calling your dictionary. Also this requires that Operation.START be hashable, but that should be the case since you described it as a class similar to an ENUM.

Bleb answered 31/7, 2015 at 14:30 Comment(0)
Q
4

Python's equivalent to a switch statement is to use a dictionary. Essentially you can store the keys like you would the cases and the values are what would be called for that particular case. Because functions are objects in Python you can store those as the dictionary values:

operation_dispatcher = {
    Operation.START: strategy_objects.StartObject,
    Operation.STOP: strategy_objects.StopObject,
}

Which can then be used as follows:

try:
    strategy = operation_dispatcher[operation] #fetch the strategy
except KeyError:
    strategy = default #this deals with the else-case (if you have one)
strategy() #call if needed

Or more concisely:

strategy = operation_dispatcher.get(operation, default)
strategy() #call if needed

This can potentially scale a lot better than having a mess of if-else statements. Note that if you don't have an else case to deal with you can just use the dictionary directly with operation_dispatcher[operation].

Quadrumanous answered 31/7, 2015 at 14:32 Comment(0)
A
4

You could try something like this.

For instance:

def chooseStrategy(op):
    return {
        Operation.START: strategy_objects.StartObject
        Operation.STOP: strategy_objects.StopObject
    }.get(op, strategy_objects.DefaultValue)

Call it like this

strategy = chooseStrategy(operation)()

This method has the benefit of providing a default value (like a final else statement). Of course, if you only need to use this decision logic in one place in your code, you can always use strategy = dictionary.get(op, default) without the function.

Aluino answered 31/7, 2015 at 14:37 Comment(2)
You're missing the call. strategy = chooseStrategy(operation)()Ilion
Thanks! I missed that he was calling the chosen strategy. I've edited my post.Aluino
Y
2

Starting from python 3.10

match i:
    case 1:
        print("First case")
    case 2:
        print("Second case")
    case _:
        print("Didn't match a case")

https://pakstech.com/blog/python-switch-case/

Yuri answered 25/10, 2021 at 13:2 Comment(0)
T
1

You can use some introspection with getattr:

 strategy = getattr(strategy_objects, "%sObject" % operation.capitalize())()

Let's say the operation is "STATUS", it will be capitalized as "Status", then prepended to "Object", giving "StatusObject". The StatusObject method will then be called on the strategy_objects, failing catastrophically if this attribute doesn't exist, or if it's not callable. :) (I.e. add error handling.)

The dictionary solution is probably more flexible though.

Tunic answered 31/7, 2015 at 14:36 Comment(1)
Checking the class exists with hasattr(...) would be the equivalent of the KeyError in the dictionnary solution.Calycle
B
0

If the Operation.START, etc are hashable, you can use dictionary with keys as the condition and the values as the functions to call, example -

d = {Operation.START: strategy_objects.StartObject , 
     Operation.STOP: strategy_objects.StopObject, 
     Operation.STATUS: strategy_objects.StatusObject}

And then you can do this dictionary lookup and call the function , Example -

d[operation]()
Biauriculate answered 31/7, 2015 at 14:32 Comment(0)
E
-1

Here is a bastardized switch/case done using dictionaries:

For example:

# define the function blocks
def start():
    strategy = strategy_objects.StartObject()

def stop():
    strategy = strategy_objects.StopObject()

def status():
    strategy = strategy_objects.StatusObject()

# map the inputs to the function blocks
options = {"start" : start,
           "stop" : stop,
           "status" : status,

}

Then the equivalent switch block is invoked:

options["string"]()
Economize answered 31/7, 2015 at 14:32 Comment(1)
But strategy will be local to each of those small functions, and won't be visible in the scope where you execute options["string"]().Adjudicate

© 2022 - 2024 — McMap. All rights reserved.