views:

1406

answers:

7

I recently read the questions that recommend against using switch-case statements in languages that do support it. As far as Python goes, I've seen a number of switch case replacements, such as:

  1. Using a dictionary (Many variants)
  2. Using a Tuple
  3. Using a function decorator (http://code.activestate.com/recipes/440499/)
  4. Using Polymorphism (Recommended method instead of type checking objects)
  5. Using an if-elif-else ladder
  6. Someone even recommended the Visitor pattern (Possibly Extrinsic)

Given the wide variety of options, I am having a bit of difficulty deciding what to do for a particular piece of code. I would like to learn the criteria for selecting one of these methods over the other in general. In addition, I would appreciate advice on what to do in the specific cases where I am having trouble deciding (with an explanation of the choice).

Here is the specific problem:
(1)

def _setCurrentCurve(self, curve):
        if curve == "sine":
            self.currentCurve =  SineCurve(startAngle = 0, endAngle = 14,
            lineColor = (0.0, 0.0, 0.0), expansionFactor = 1,
            centerPos = (0.0, 0.0))
        elif curve == "quadratic":
            self.currentCurve = QuadraticCurve(lineColor = (0.0, 0.0, 0.0))

This method is called by a qt-slot in response to choosing to draw a curve from a menu. The above method will contain a total of 4-7 curves once the application is complete. Is it justified to use a throw away dictionary in this case? Since the most obvious way to do this is if-elif-else, should I stick with that? I have also consider using **kargs here (with a friends help) since all the curve classes use **kargs...

(2)
This second piece of code is a qt-slot that is called when the user changes a property of a curve. Basically the slot takes the data from the gui (spinBox) and puts it in an instance variable of the appropriate curve class. In this case, I again have the same question - should I use a dict?

Here is the aforementioned slot-

def propertyChanged(self, name, value):
    """A Qt slot, to react to changes of SineCurve's properties."""
    if name == "amplitude":
        self.amplitude = value
    elif name == "expansionFactor":
        self.expansionFactor = value
    elif name == "startAngle":
        self.startAngle = value
    elif name == "endAngle":
        self.endAngle = value

For reference, here is the code for connecting to the above slot -

def _connectToPage(self, page):
    for connectionData in page.getConnectibles():
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    lambda value, name = connectionData["property"]:\
                        self.currentCurve.propertyChanged(name, value))
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    self.hackedDisplayArea.update)

Note - The self.endAngle etc. are initialized in the constructor.

As far as I know, the reasons for choosing a dict is for fast lookup. When is that warranted? when I have 100 cases or more? Is it a good idea to keep building and throwing away a dictionary each time the function is called? If I build a dict for this purpose outside a function, should I check If it is needed elswhere? What happens if it is not needed elsewhere?

My question is what is the best-practice if there is one? What is the best/most elegant way to go about things? Put in yet another way, when to use if-elif-else, when to use each of the other options?

+6  A: 

In the first example I would certainly stick with the if-else statement. In fact I don't see a reason not to use if-else unless

  1. You find (using e.g. the profile module) that the if statement is a bottleneck (very unlikely IMO unless you have a huge number of cases that do very little)

  2. The code using a dictionary is clearer / has less repetition.

Your second example I would actually rewrite

setattr(self, name, value)

(probably adding an assert statement to catch invalid names).

dF
+1 I didn't think of setattr.
batbrat
Thanks a lot for the answer. It was really helpful. If I could have marked two answers as correct, I would have marked yours too. As it is I cant :(. Anyhow, I really appreciate your help.
batbrat
+1  A: 

Each of the exposed options fit well some scenarios:

  1. if-elif-else: simplicity, clarity
  2. dictionary: useful when you configure it dynamically (imagine that you need a particular functionality to be executed on a branch)
  3. tuple: simplicity over if-else case for multiple choices per branch.
  4. polymorphism: automatic object oriented branching
  5. etc.

Python is about readability and consistency and even if your decision will always be a subjective and it will depend on your style, you should always think about Python mantras.

./alex

alexpopescu
Thanks for giving me a guideline.
batbrat
I'm learning and reminding them myself each and every day. One aspect of a code base (that sometimes is ignored) is about collaboration and clear communication.
alexpopescu
One more thing. What do you mean by dynamic configuration? I think I follow, just to be clear, could you please comment?
batbrat
@batbrat: Dynamic configuration == code not in your program. Code in a configuration file that's brought in at run time. Not too useful in Python, but very useful for statically compiled languages.
S.Lott
Thanks S.Lott - Got it :)
batbrat
@batbrat Think of it as a dynamic if: {'branch1' : func1, 'branch2' : func2, ...}. The keys are the domain of your possible values, but the code executed for each branch can be dynamically set.
alexpopescu
+1  A: 

I agree with df regarding the second example. The first example I would probably try to rewrite using a dictionary, particularly if all the curve constructors have the same type signature (perhaps using *args and/or **kwargs). Something like

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](options_here)

or perhaps even

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](**preset_curve_defaults[new_curve])
kquinn
I've considered that. However, In the first example, I would have to put in all the construction options for any curve that might be constructed. Is that a good idea?eg: self.currentCurve = self.preset_curves[new_curve](startAngle =10, ...., endX =120)
batbrat
+1 for confirming my suspicsion that **kargs could help.
batbrat
Seems to me that's what default arguments on the constructor itself are for.
kquinn
Ok. Thanks. I do have some nice default arguments set up on the constructor. Thats an idea I didn't consider in depth.
batbrat
+2  A: 

Considering that this is done in response to a user action (pickings something from a menu), and the number of choices you anticipate is very small, I'd definitely go with a simple if-elif-else ladder.

There's no point in optinizing for speed, since it only happens as fast as the user can make the selection anyway, this is not "inner loop of a raytracer"-territory. Sure, it matters to give the user quick feedback, but since the number of cases is so small, there is no danger of that either.

There's no point in optimizing for conciseness, since the (imo clearer, zero-readability-overhead) if-ladder will be so very short anyway.

unwind
I agree. Don't fix it if it ain't broken.
sykora
+2  A: 

Regarding your dictionary questions:

As far as I know, the reasons for choosing a dict is for fast lookup. When is that warranted? when I have 100 cases or more? Is it a good idea to keep building and throwing away a dictionary each time the function is called? If I build a dict for this purpose outside a function, should I check If it is needed elswhere? What happens if it is not needed elsewhere?

  1. Another issue is maintainability. Having the string->curveFunction dictionary allows you to data-drive the menu. Then adding another option is just a matter of putting another string->function entry in the dictionary (which lives in a part of the code dedicated to configuration.

  2. Even if you have only a few entries, it "separates concerns"; _setCurrentCurve is responsible for wiring up at run time, not defining the box of components.

  3. Build the dictionary and hold onto it.

  4. Even if it's not used elsewhere, you get the above benefits (locatability, maintainability).

My rule of thumb is to ask "What's going on here?" for each component of my code. If the answer is of the form

... and ... and ...

(as in "defining the library of functions and associating each with a value in the menu") then there are some concerns begging to be separated.

joel.neely
+1 Good point. I was wondering about other benefits. However, I could 'data drive' the menu like you said, but that might be overkill, since there are only a few curves anyway (and likely to stay that way in the future). D9o share your thoughts on this. Thanks.
batbrat
Thanks again! The data driven menu idea will be of great help in one of my other projects.
batbrat
+11  A: 

Sigh. Too much hand-wringing over the wrong part of the problem. The switch statement is not the issue. There are many ways of expressing "alternative" that don't add meaning.

The issue is meaning -- not technical statement choices.

There are three common patterns.

  • Mapping a key to an object. Use a dictionary if it is almost totally static and you have a mapping between a simple key and another more complex thing. Building a dictionary on the fly each time you need it is silly. You can use this if it's what you mean: your "conditions" are simple, static key values that map to objects.

  • Variant behavior among subclasses. Use Polymorphism instead of type checking objects. Correct. If you have similar objects in multiple classes with variant behavior, they should be polymorphic. Use this as often as possible.

  • Other variant behavior. Use an if-elif-else ladder. Use this when you don't have largely static key-to-value mapping. Use this when the conditions are complex, or you mean procedures, not objects.

Everything else is just tricky code that can achieve similar results.

Using a Tuple. This is just dictionary without the mapping. This requires search, and search should be avoided whenever possible. Don't do this, it's inefficient. Use a dictionary.

Using a function decorator (http://code.activestate.com/recipes/440499/). Icky. This conceals the if-elif-elif nature of the problem you're solving. Don't do this, it isn't obvious that the choices are exclusive. Use anything else.

Someone even recommended the Visitor pattern. Use this when you have an object which follows the Composite design pattern. This depends on polymorphism to work, so it's not really a different solution.

S.Lott
If I use a simple static key to value mapping, its still a bad idea to build on the fly isn't it? In that case, wouldn't I still have to use an if-else, unless I stored the dictionary somewhere and use it in two or more places? Thanks for the detailed answer.
batbrat
Just to be sure, I should not do something like {key1: function1, key2:function2}.get(...,...) right?
batbrat
Building a dictionary on the fly is unlikely to be what you **mean**. If you mean if-elif-elif then just say that. If your **meaning** is "This Key always maps to This Function" then say that as an official first-class part of your program.
S.Lott
@batbrat: Just to be sure -- two years from now, someone else is going to read that code and ask "What did they **mean** by that>?" Make the meaning so crystal clear you don't have to answer questions from anyone ever. Software captures knowledge. Represent that knowledge as clearly as possible.
S.Lott
Thanks for clarifying. I get it now. You are right. I didn't mean on the fly. I meant constructing the dictionary inside the function. Clearly, that is not what you mean. You would use it only if that kind of mapping is really meaningful and put it somewhere outside of the function. Right?
batbrat
Sorry to bother you, but have I gotten it right in the previous comment i.e. so long as it is meaningful, put the dict outside the function (or inside the function, if that really does have more meaning - I find that hard to imagine...)?
batbrat
@batbrat: Constructing a dictionary on the fly is silly because it just replaces "if x: y-elif-elif" with "{ x:y, }". No improvement in clarity. If the dictionary is built as part of a configuration, or provided from some external source, then you might use it.
S.Lott
Thanks S.Lott. I am totally clear now. :)
batbrat
@S.Lott Thanks again for all your help.
batbrat
+1  A: 

In Python, don't event think about how to replace a switch statement.

Use classes and polymorphism instead. Try to keep the information about each availble choice and how to implement it in one place (i.e. the class that implements it).

Otherwise you will end up having lots of places that each contain a tiny fraction of each choice, and updating/extending will be a maintenance nightmare.

This is exactly the kind of problem that OOD tries to solve by abstraction, information hiding, polymorphism and the lot.

Think about what classes of objects you have and their properties, then create an OO architecture around them. This way you will never ever have to worry about a missing "switch" statement again.

Ber