Feed Sign in with OpenID OpenID

Simon Willison’s Weblog

Switch statements in Python

Python doesn’t support a native switch statement. I’ve found myself using the following coding idiom instead recently which seems to work pretty well:

{'option1': function1,
 'option2': function2,
 'option3': function3,
 'option4': function4}[value]()

This works with lambdas as well, for inline calculations. Here’s a switch statement that assigns to a variable in PHP:

switch ($value) {
    case 'a':
        $result = $x * 5;
        break;
    case 'b':
        $result = $x + 7;
        break;
    case 'c':
        $result = $x - 2;
        break;
}

And here’s the equivalent code in Python:

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

This is Switch statements in Python by Simon Willison, posted on 7th May 2004.

View blog reactions

Next: Google approved PageRank stripping

Previous: What makes a geek?

35 comments

  1. Yeah, the lack of switch() through me for a bit of a loop when first starting in Python, but all said and done, I have found I don't miss it all that much. Especially when I can use a dictionary map in it's place if I have to. The syntax is a little weird if you're coming from a c-ish background, but one must admit that it's much more concise and not as overly verbose as a lot of languages are with switch().

    Dave Giffin - 7th May 2004 04:18 - #

  2. What about a default entry, if value is not in the hash?

    Dominic Mitchell - 7th May 2004 07:42 - #

  3. That's where things get a little messy. You can do a default clause by wrapping the whole code chunk in a try/catch handler, but that can lead to nasty problems if one of the functions called from within the block throws an uncaught KeyError exception of its own (those can be a real sod to debug if you're catching them accidentally at a higher level). The code ends up looking like this:

    try:
        {'option1': function1,
         'option2': function2,
         'option3': function3,
         'option4': function4}[value]()
    except KeyError:
        # default action

    Simon Willison - 7th May 2004 08:10 - #

  4. You can also use the get method. It would be (not tested)
    {'option1': function1,
     'option2': function2,
     'option3': function3,
     'option4': function4,
    }.get(value, defaultfunction)()
    

    Josu Oyanguren - 7th May 2004 08:28 - #

  5. I also like this construct:
    
    ((value == 'option1' and function1) or
     (value == 'option2' and function2) or
     (value == 'option3' and function3) or
     defaultfunction)()
    
    
    But it's more verbose and less efficient.

    Roberto - 7th May 2004 08:56 - #

  6. Creating and throwing away a dictionary each time makes me a bit nervous about efficiency. But maybe the compiler recognizes constant dictionaries and optimizes their creation?

    There is also the variations on

    result = getattr(self, 'function_' + value)(x)

    where your functions are now named function_option1, function_option2, and so on.

    Damian Cugley - 7th May 2004 10:56 - #

  7. In situations where you could use a C-style switch statement (ie where you're just relying on imperative effects of the code, and if you want a result you have to assign it to a variable) you can use a chain of if and elif statements, like:

    if value == 'option1':
        function1()
    elif value == 'option2':
        function2()
    elif value == 'option3':
        function3()

    This seems as concise as switch to me, and also has the advantage that you can use other tests as well as equality. It also allows you to have a default response if the value isn't one of the ones tested; with a dictionary this would throw an exception, which is maybe what you'd want anyway. A possible disadvantage is that if the value you're switching on is an expression, it'll be evaluated more than once (and if it has side-effects, they'll occur more than once) so if this is the case you'll want to assign it to a variable first.

    The dictionary method is nice where you want it to return a result directly, though, as in your example with the lambdas. For the best of both worlds, it would be good to have something like Lisp's cond.

    Andy Smith - 7th May 2004 15:18 - #

  8. Lambda inherits the environment-- it would probably be better to not pass any arguments to the lambda function:

    result = {
      'a': lambda : x * 5,
      'b': lambda : x + 7,
      'c': lambda : x - 2
    }[value]()
    

    That said, I like if/elif/else better.

    John Doty - 7th May 2004 17:11 - #

  9. Interestingly enough, it works very similarly in Javascript:
    var x = 10 ;
    var y = 20 ;
    var operator = "*"
    var result = {
    	"+" : function(x, y) { return x + y } ,
    	"-" : function(x, y) { return x - y } ,
    	"*" : function(x, y) { return x * y } ,
    	"/" : function(x, y) { return x / y } 
    }[operator](x, y) ;

    Tom Trenka - 7th May 2004 18:41 - #

  10. John-- you must be old school. Who needs a stinkin' switch when we've got GOTO?

    I'm thinkin' fortran here. :)

    Jeremy Dunck - 8th May 2004 02:55 - #

  11. So how do you simulate a switch statement where there are cases and no breaks?

    Lach - 8th May 2004 02:59 - #

  12. Lach: that's a really good question. Off the top of my head I can't think of a way of doing it that doesn't involve a big ugly nest of if statements. I'm surprised I've never found myself wanting to use one in my Python programming.

    Simon Willison - 8th May 2004 04:31 - #

  13. Yeah, I was wondering about that: How would one simulate fall-through, and avoid scope problems. (With given approach, if you're using JavaScript, you can't use this directly. Does Python have similar problems?)

    liorean - 8th May 2004 15:08 - #

  14. switch = {
    '1': (lambda, False),
    '2': (lamda2, False),
    '3': (lamda3, True),
    '4': (lambda4, False)
    }
    
    fall = False
    for key, val in switch:
        if x == key or fall:
            val[0]()
            fall = val[1]
    

    perhaps? Still, that's nowhere near as compact as the other examples.

    Lach - 9th May 2004 02:00 - #

  15. Sorry, but I hate all of them! Verbosity isn't a inherit problem of a programming language... It is an advantage when used correctly. In practice simple syntaxes don't really save much time for the programmer, but they often hard to read and document and easy to mess up the syntax. (Hence, more bugs.) Of couse, the programmer could add stuff which isn't needed (var type often isn't necessary, inconsistant syntax or synonyms that have different functions, etcetera) and write poorly written verbose code. However, simple code is bad too.

    I even dislike the traditional syntax of switch statements. They come from a time of goto and labels. Switch statments must be read linearly and completely to understand how the program flows (i.e. break keywords in unsusal places). This leads to spaghetti code. All the examples here are worse (for traditional romanic/germanic speaking people) with the possibilities before the question!

    Besides, I gather than most compilers can easily optimize "switch-like" if-then-elseif blocks. Let it go.

    Jimmy C. - 9th May 2004 05:30 - #

  16. I've always preferred table driven methods like your dictionary of function references even in languages like C, which have switch statements, if there are more than 3-4 alternatives. Data, such as the table above, is always easier to understand and modify than code, like a switch statement.

    As one comment above mentions, switch statements in most languages are also quite limited in their tests, though I was impressed by Damian Conway's ability to add a switch statement to perl, which offered most types of tests that you might want in a modern language, from regular expressions to list and dictionary membership, without modifying the interpreter. See Switch.pm for details.

    James Walden - 9th May 2004 21:24 - #

  17. In PHP it's possible to put a complete expression in the case statements. Just an example to illustrate that :
    $str = "abc";
    switch( $str )
    {
    	case "aaa":
    	{
    		echo "\$str = 'aaa'";
    		break;
    	}
    	case substr( $str, -1 )=="c":
    	{
    		echo "\$str ends with a 'c'";
    		break;
    	}
    	default:
    	{
    		echo "default";
    		break;
    	}
    }
    You can also use some 'complex' things like the in_array function or whatever you want.

    NB: I like to put some {} to clearly delimit the blocks of statements of the various cases

    Mathieu 'P01' HENRI - 10th May 2004 00:40 - #

  18. As a general rule, I don't bother going through simulating a switch unless I have a large list of possible options, say a day of the month or some such. In cases where there are only likely to be 2 to 10 options, I usually just use an if, elif, else chain. One benefit is that it allows a much more in depth level of testing. And all said and done, I haven't noticed any serious (or even mild) performance hit doing this. Now, if the test would need to be against say, 1 through 31 or 1 through Z, well, then I would roll the dictionary, as it would likely be a performance improvement, less overall code and clearer to read later, but otherwise, why bother for most things?

    Python is an elegant language compared to most others. I don't often find myself missing the switch() function very often. I just keep coding and when I hit something I would have used a switch() for in another language, it seems it's just not needed in Python.

    Dave Giffin - 10th May 2004 15:01 - #

  19. The answer that I have received in the past is that switches are rarely needed in a language that supports polymorphism. The switch statement is a procedural crutch for lack of object differentiation. I the example above each "x" would be a different subclass, and the proper function would be called based on the subclass's definition of the function.

    There's a common refactoring based on this: ReplaceConditionalWithPolymorphism.

    Rob - 10th May 2004 19:54 - #

  20. Using polymorphism to switch seems a bit overboard. Maybe a happy medium is creating methods for each case rather than subclasses. It's really handy in Ruby.

    For example, a common use of a case statement is for command-line actions. So, let's say you're building a command-line time clock tool. With 'in', 'out' and 'edit' commands.

      def in_action( client_name )
        .. db call to handle clocking in ..
      end
      def out_action()
        .. db call to clock out ..
      end
      def edit_action( time_entry_id, client_name )
        .. db call to update the entry ..
      end
    

    Then, in lieu of my original case statement:

      cmd, *opts = ARGV
      if respond_to? cmd
        method( cmd ).call( *opts )
      else
        default_action
      end
    

    I feel like case statements are four-way stops. They direct traffic in a simple fashion. So, with methods, you can build traffic lights and crossing guards in easily.

    Like with the above, I could add explanatory text for each option by adding explain methods:

      def in_explain; "Clock in"; end
      def out_explain; "Clock out; end
      def edit_explain; "Edit a time entry"; end
    

    The example's sorta contrived, but I think it's another alternative worth considering.

    _why - 11th May 2004 00:56 - #

  21. If the vertical length of the code is problem, the if/elif syntax can be compressed a little:

      if   value == 'a': result = x * 5
      elif value == 'b': result = x + 7
      elif value == 'c': result = x - 2
    

    I quite like this syntax, though having the suite on the same line as the test may offend your sense of style.

    Alan Green - 11th May 2004 01:42 - #

  22. I find myself using the following idiom, which allows me to do tricky C switch things like fall through from one case to another:

    for value in [value]:
      if value == 'a':
        result = x * 5
        break
      if value == 'b':
        result = x + 7
        break
      if value == 'c':
        result = x - 2
        break
      # default
      result = x
      break
    

    OK, that first line is a little contrived, but there's typically something more complex than [value] up there on the first line. I'm often testing the result of a more complex expression (like switch(expr) in C) so it's useful to capture that computation once.

    It doesn't quite fall through like C switch (the test you fall into still has to succeed). But, hey, it does use the break keyword.

    Dan Sandler - 11th May 2004 14:10 - #

  23. Cool. -Joe joe@ucs.cc

    Joe - 14th May 2004 21:26 - #

  24. is that nice?
    (cond1 and do1()) or (cond2 and do2()) ...?????
    and what do you do if do1() return a false value? so you have to write (cond1 and do1() or True) or ....
    KISS and use if
    what about:
    iFooBar = ......
    if iFooBar < 0:
    do
    else if iFooBar==1:
    do
    else if iFooBar==2:
    do
    else :
    do
    #
    where is the different to switch ?
    break? nobody like break

    Floirian - 18th May 2004 16:04 - #

  25. Why one should use python? In which place python is more applicable?

    Tushar - 14th June 2004 18:29 - #

  26. I really have to agree with Jimmy C here, there are some neat tricks here, but I'd hate to be the one maintaining your code. I think it's a good idea to explore them though, because this kind of exploration makes sure you really know the language/compiler/interpreter you're dealing with.

    For those wondering about how to simulate fall-through case statements: I think it's interesting to note that the C# designers chose to explicity disallow fall-through in switch statements. Every code block associated with a case label in a C# switch must end with a break or a goto:

    switch(var)
    {
        case 1:
           // ...
           break;
        case 2:
           // ...
           goto case 3; // simulate fall-through
        case 3:
           // ...
        default:  // compiler error here -- fall-through == bad!
           // ...
    }
    

    I believe this is to allow people to re-order the case clauses willy-nilly, and to make sure you don't accidentally generate fall-through code by leaving out a break. Another case where verbosity is required for readability (and I like it..).

    Stewart Johnson - 2nd December 2004 19:23 - #

  27. i would love to correspond with you and get all the information that you have on c programming

    aguvaza_a@yahoo.com - 21st February 2005 12:35 - #

  28. this is good young man

    tafa - 30th May 2005 18:51 - #

  29. Actually, C# still allows fall-through in switch statements, just for empty cases or with goto ;). For example you can write:
    switch (value)
    {
        case 'A':
        case 'B':
        case 'C':
            doSomething();
            break;
    
        case 'D':
            doSomethingBefore();
            goto 'A';
            break;
    
        default:
            break;
    }
    

    Julio César Carrascal - 17th August 2005 04:47 - #

  30. Argh. The lack of a switch statement is one of my few complaints about Python (along with the lack of a ternary operator). While dictionaries, polymorphism, etc can often do the work of a switch statement, and sometimes should be used instead of a switch statement, there are many times when a switch is a better way to go and it makes the code easier to read. Dictionaries lack fall-thru, polymorphism adds a lot of complexity that is often not needed, and multiple if statements are overly verbose and prone to programmer error. The refusal to add a simple switch statement to Python has to be one of the most ridiculous decisions that has been made in the design of this otherwise great language. Why does a language that encourages simplicity over complexity ask programmers to figure out alternative methods to a long-standing and simple method of handling a common problem? This whole discussion having needed to exist at all proves that Python should have a switch statement.

    MikeFM - 21st August 2005 01:55 - #

  31. Lack of ternary operator?

    Do you realise that:

    test ? true : false

    is equivalent to the following python code?

    test and true or false

    sulla - 28th August 2005 08:52 - #

  32. Very useful little snippet here. I've even mentioned it in a recent blog post. I'm only just starting on Python at the moment myself so I'm certainly no expert.

    Straw Dogs - 6th September 2005 23:10 - #

  33. Re ternary operator:

    >Do you realise that:

    > test ? true : false

    >is equivalent to the following python code?

    > test and true or false

    Perhaps they are equivalient but be honest - which is more _readable_?

    This is at least one case where the "old fashioned" ternary operator beats short circuit boolean evaluation hands down...

    I would also suggest switch() statements have a certain simple syntactic elegance (although the C implementation has irritating limitations)

    my 0.02 cents worth

    kim - 9th October 2005 16:46 - #

  34. Do you realise that:

    test ? true : false

    is equivalent to the following python code?

    test and true or false

    This doesn't work out very well if your then-value happens to be considered false (eg empty strings or lists; or None):

    >>> condition = True

    >>> thenval = ''

    >>> elseval = 'oops'

    >>> print condition and thenval or elseval

    oops

    edward - 14th October 2005 07:07 - #

  35. This "switch" replacement came in handy for a simple text based menu i did for a quick program ...
    #listing : simple_menu.py **************************
    import sys
    
    def menuLoop(prompt, menu, data):
    	while 1:
    		cmdstr = raw_input(prompt)
    		cmd = cmdstr[0]
    		result = True
    		if len(cmdstr) > 0:
    			if menu.has_key(cmd):
    				result = menu[cmd](cmdstr, data)
    			else:
    				print "Unknown command, type h for help."
    				
    		if not result:
    			sys.exit(0);
    
    #listing:main_menu.py **************************
    from simple_menu import *
    
    def mainMenu_DisplayHelp(cmd, data):
    	print "Help!"
    	print "you type %s" %cmd
    	return True;
    	
    def mainMenu_Exit(cmd, data):
    	return False;
    
    def mainMenu_DisplayExports(cmd, data):
    	exports = data["db"].getExports()
    	print exports	
    	return True;
    	
    def mainMenu(data):
    	menuLoop("main :", {'h': mainMenu_DisplayHelp,
    			    '?': mainMenu_DisplayHelp,
    			    'e': mainMenu_DisplayExports,
    			    'x': mainMenu_Exit}, data)
    
    #listing main.py: **************************
    
    import main_menu
    
    db = create_the_db()
    main_menu.mainMenu({"db":db})
    
    

    David Haler - 19th December 2005 22:44 - #

Comments are closed.

Previously hosted at http://simon.incutio.com/archive/2004/05/07/switch

A django site