Welcome, guest | Sign In | My Account | Store | Cart

Here is yet another way to emulate a switch-case statement, perhaps one you might not have thought of.

Python, 49 lines
 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[0] == value: return exclass
   return None

def multicase(*values):
   exclass, exobj, tb = sys.exc_info()
   if exclass is case_selector and exobj.args[0] 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)])[0]

(And these are definitely not the weirdest possible.)

After all, we're all creatures of habit, aren't we?

Cheers and happy switching!

9 comments

Harry Fuecks 18 years, 11 months ago  # | flag

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."

Oren Tirosh 18 years, 11 months ago  # | flag

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.

Walter Brunswick 18 years, 11 months ago  # | flag

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.)

Zoran Isailovski (author) 18 years, 11 months ago  # | flag

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".

Zoran Isailovski (author) 18 years, 11 months ago  # | flag

Unpacking values. Extending case(value) to case(*values) and checking if any of the values matches exobj.args[0] will enable code like

except case(a, b, c):
   ...
except case(*seq):
   ...

I'll add this to the recipe.

Zoran Isailovski (author) 18 years, 11 months ago  # | flag

Added ... ... as extra function:

except multicase(*values)

is slower then case(), hence the decision to have an extra function.

Raymond Hettinger 18 years, 11 months ago  # | flag

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:

while 1:
    n = raw_input('Enter an integer: ')
    int(n)                  # verify that n is an integer
    try:
        raise n
    except ('1', '2', '3'):
        print "You entered a number between 1 and 3"
    except '4':
        print "You entered 4"
    except '5':
        print "You entered 5"
    except ('6', '7', '8', '9'):
        print "You entered a number between 6 and 9"
    except:
        print "You entered a number less than 1 or greater then 9"
Raymond Hettinger 18 years, 11 months ago  # | flag

Correction. Due to a nuance of how string exceptions work, the above example should raise an interned version of the string:

raise intern(n)
Zoran Isailovski (author) 18 years, 11 months ago  # | flag

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:

try:
  raise intern(str(n))
except ('1', '2', '3'):
  ...

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).

Created by Zoran Isailovski on Mon, 25 Apr 2005 (PSF)
Python recipes (4591)
Zoran Isailovski's recipes (13)

Required Modules

Other Information and Tasks