Here is yet another way to emulate a switch-case statement, perhaps one you might not have thought of.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
import sys class case_selector(Exception): def __init__(self, value): # overridden to ensure we've got a value argument Exception.__init__(self, value) def switch(variable): raise case_selector(variable) def case(value): exclass, exobj, tb = sys.exc_info() if exclass is case_selector and exobj.args == value: return exclass return None def multicase(*values): exclass, exobj, tb = sys.exc_info() if exclass is case_selector and exobj.args in values: return exclass return None if __name__ == '__main__': print def InputNumber(): while 1: try: s = raw_input('Enter an integer') except KeyboardInterrupt: sys.exit() try: n = int(s) except ValueError, msg: print msg else: return n while 1: n = InputNumber() try: switch(n) except ( case(1), case(2), case(3) ): print "You entered a number between 1 and 3" except case(4): print "You entered 4" except case(5): print "You entered 5" except multicase(6, 7, 8, 9): print "You entered a number between 6 and 9" except: print "Youe entered a number less then 1 or grater then 9"
A switch-case statement (or the lack of it) seems to be a controversal point among Python users. There are several dictionary-based substitute proposals, and even one that quite closely emulates the C syntax (using a FOR loop!!!; you need to take a look at this one, it's brilliant and it's on http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410692 ).
I personally do not extensivelly use switch-case constructs in any language, so I do not feel real pain about Python not supporting it. To me, the really interesting aspect of those substitutes is that they demonstrate what is all possible with Python, showing Python's power when it comes to "using it as a little language" (i.e. adding constructs in Python itself that emulate features which are hard-coded parts of other languages).
Some words about this recipe: Of cource it seems (very) weird to see except clauses instead of case selectors. But, on the other hand, how weird might statements like these look to a C or VB programmer:
return (lambda x: sqrt(-x), sqrt)[x >= 0] return (x < 0 and [sqrt(-x)] or [sqrt(x)])
(And these are definitely not the weirdest possible.)
After all, we're all creatures of habit, aren't we?
Cheers and happy switching!
Just being snarky... Think this is worth bearing in mind: http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl
In particlar the remark;
"Perhaps more important, it's not what compiler implementors expect. They expect exceptions to be set up often but thrown rarely, and they usually let the throw code be quite inefficient. Throwing exceptions is one of the most expensive operations in Java, surpassing even new."
Not relevant to python. That comment may be relevent to Java or C++ but exceptions aren't "exceptionally" slow in Python. Consider the fact that Python uses an exception to mark the end of every for loop.
Some disadvantages... Using exceptions for case switches brings some disadvantages, for example, you cannot use the asterisk for unpacking values "except case(*string.letters)" as you can do in Brian Beck's "for loop" case switch clause... Any way to overcome this disadvantage? ("reduce(case,[string.letters])" does not seem to work either.)
About exceptions in Python and elsewhere. I've benchmarked the exception-based variant with 10 cases against an if-elif variant. On Python 2.2.2, there is a factor of about 12.7 in favor of if-elif, so yes, exceptions are slower. Yet in python this is a reasonable overhead. On .NET for example, the factor is about 3500 (no typo, I repeat: 3500).
I wonder why "compiler implementors expect... exceptions to be set up often but thrown rarely, and they usually let the throw code be quite inefficient." This is an illusionary expectation, because even the implementors of the standard platform libraries use exceptions for all kinds of assertions, which is only natural. There is no point in adding a (widely and readily accepted) feature to a language, but then let it be "quite inefficient".
The Python developers obviously did understand this and made the throw code quite efficient, hence the preference of the idiom "Easier to Get Permission" to "Look Before You Leap".
Unpacking values. Extending case(value) to case(*values) and checking if any of the values matches exobj.args will enable code like
I'll add this to the recipe.
Added ... ... as extra function:
is slower then case(), hence the decision to have an extra function.
Simplify and speed-up with string exceptions. The recipe runs slowly because all cases must be instantiated on every pass. Using string exceptions is much faster and does not require any wrapping logic:
Correction. Due to a nuance of how string exceptions work, the above example should raise an interned version of the string:
Good hint, but not generally usable. That will not work for types other then strings, which is not in the "spirit" of a switch-case.
However, since a switch-case is usually restricted to simple types in other languages, im many cases a variant of your suggestion may be utilized:
However, that might not work for objects that define their own __str__, while the code in this recipe works with any objects (to which the equalty operator may be reasonably applied).