1717

I want to write a function in Python that returns different fixed values based on the value of an input index.

In other languages I would use a switch or case statement, but Python does not appear to have a switch statement. What are the recommended Python solutions in this scenario?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Michael Schneider
  • 17,929
  • 3
  • 24
  • 14
  • 77
    Related PEP, authored by Guido himself: [PEP 3103](http://www.python.org/dev/peps/pep-3103/) – chb Jun 16 '12 at 17:22
  • 28
    @chb In that PEP, Guido doesn't mention that if/elif chains are also a classic source of error. It's a very fragile construct. – itsbruce Jan 09 '14 at 13:46
  • 15
    Missing from all solutions here is detection of **duplicate case values**. As a fail-fast principle, this may be a more important loss than performance or the fallthrough feature. – Bob Stein Oct 17 '14 at 19:04
  • 6
    `switch` is actually more "versatile" than something returning different fixed values based on the value of an input index. It allows for different pieces of code to be executed. It actually does not even need to return a value. I wonder if some of the answers here are good replacements for a general `switch` statement, or only for the case of returning values with no possibility of executing general pieces of code. – sancho.s ReinstateMonicaCellio May 14 '17 at 21:29
  • 1
    @sancho.s - agreed. If you're not returning or breaking from your `switch` `case` statement then *all* code in the remaining cases will be executed. That is clearly not the same as a collection of if/elseifs – BrynJ Aug 25 '17 at 09:01
  • 3
    In the same way, syntax like Ruby's case...when... (or Scala's match, Haskell's case, Perl's given/when) meet a common use case and offer a powerful abstraction. if...elif... is a poor substitute. – itsbruce Oct 01 '17 at 07:14
  • 1
    Python's aphorism that "explicit is better than implicit" is what makes fall-through in Python non-standard. I don't know if I should be sad or happy :| – John Strood Jul 09 '18 at 08:24

44 Answers44

2153

Python 3.10 (2021) introduced the match-case statement, which provides a first-class implementation of a "switch" for Python. For example:

def f(x):
    match x:
        case 'a':
            return 1
        case 'b':
            return 2
        case _:
            return 0   # 0 is the default case if x is not found

The match-case statement is considerably more powerful than this simple example.


If you need to support Python ≤ 3.9, use a dictionary instead:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 137
    What happens if x is not found? – Nick Sep 19 '08 at 15:46
  • 64
    @nick: you can use defaultdict – Eli Bendersky Sep 19 '08 at 17:38
  • 453
    I'd recommend putting the dict outside of the function if performance is an issue, so it doesn't re-build the dict on every function call – Claudiu Oct 23 '08 at 16:22
  • 76
    @EliBendersky, Using the `get` method would probably be more normal than using a `collections.defaultdict` in this case. – Mike Graham Feb 23 '12 at 16:38
  • 44
    @Nick, An exception is thrown—do `}.get(x, default)` instead if there should be a default. (Note: this is a lot nicer than what happens if you leave the default off a switch statement!) – Mike Graham Feb 23 '12 at 16:39
  • 4
    The dictionary that implements the switch can also be put as the default value of the function. This way it is as efficient as Claudiu mentioned in the above comment, but it is still local to the function. The problem is when anyone tries to pass the dictionary argument that was not meant to be used from outside :) The life is full of tradeoffs. – pepr Jul 14 '12 at 16:05
  • 1
    I can't get it to work properly when using a `print` statement inside the dictionary. http://ideone.com/aL9EUE – Anderson Green Aug 11 '13 at 16:54
  • 4
    @AndersonGreen: That's a different problem. This question asked about returning "fixed values". You will have to use a different solution if you want to use `print` (because `print` has side effects, and the whole dictionary is evaluated when it is constructed). – Greg Hewgill Aug 11 '13 at 20:18
  • @GregHewgill Luckily, someone already found a solution: http://stackoverflow.com/a/11479840/975097 – Anderson Green Aug 11 '13 at 20:30
  • When you're building a big dictionary for this and you want fall throughs, this will come in handy: http://stackoverflow.com/a/2974151/724752 – David Winiecki Sep 12 '14 at 15:42
  • You could also simply use `someDict.getitem(x)`, or better yet, `someDict[x]` – jpaugh Dec 02 '15 at 23:08
  • 1
    @pepr: In Python 3 with [PEP 3102](https://www.python.org/dev/peps/pep-3102), you can avoid the risk of accidentally stomping a default argument by using keyword-only arguments, e.g. `def f(x, *, _lookup={'a': 1, 'b': 2}): return _lookup.get(x, 3)`. The `*` without a name means that arguments after that point can only be passed by name, so calling `f(1, {})` won't silently misbehave. A user could do `f(1, _lookup={})`, but that's their fault. Underscore prefixes usually mean "internal use only", and we're all adults here; if they want to ignore that rule, that's their prerogative. – ShadowRanger Jun 03 '16 at 17:46
  • 1
    Of course, in this limited case, you don't even need the Python level function, you could just bind the `dict` methods directly, e.g., with no default case, `f = {'a': 1, 'b': 2}.__getitem__`, or with a default value, `f = collections.defaultdict(lambda: 3, {'a': 1, 'b': 2}).__getitem__` (though the latter will store auto-vivified key/value pairs, so a wrapper function makes sense here to avoid that). – ShadowRanger Jun 03 '16 at 17:53
  • 1
    Can you include an example of how this pseudo-switch statement would be used? – Stevoisiak Feb 01 '18 at 19:08
  • This is the general solution, not just for Python, but for language in general. I don't think people understand the actual use-cases for switches. – Woody1193 Aug 30 '18 at 22:21
  • If the actions are non-trivial, you could have the dictionary values be functions and invoke the appropriate one (a [jump/branch table](https://en.wikipedia.org/wiki/Branch_table)); anyone care to post an example? – Mawg says reinstate Monica Feb 11 '20 at 05:53
  • 1
    you can use match sooner or later: https://stackoverflow.com/questions/59098889/can-i-use-rusts-match-in-python/59099603#59099603 – sailfish009 Aug 24 '20 at 03:55
  • x10 inefficient compared w/if chain – roberto Nov 20 '20 at 09:15
  • The [match syntax](https://www.python.org/dev/peps/pep-0636/#appendix-a-quick-intro) has been accepted and will soon be officially supported in python 3 ! – Mathieu Rollet Feb 26 '21 at 14:50
  • 1
    Dictionary is not equivalent in that all the value expressions are evaluated. This can result in an exception if an expression is dependent on the key, as well as poor performance. The equivalent is if/elif/else. – Chris Mar 22 '21 at 11:02
  • 5
    **As of Python 3.10.0 there's a single official syntactical way now** making the workarounds effectively redundant for this release: The `match`-`case` construct. [In this post](https://stackoverflow.com/a/66877137/6685358) I try to cover everything to know about it - including **behavior you might not expect** (i.e. all cases `break` automatically). Unfortunately, new answers can't be added anymore and my edit on this answer here got rejected - so I have to add it as a comment and hope that anyone makes their way to the comment section. – fameman Apr 03 '21 at 11:11
  • 1
    i'd add that'd only work if all keys are hashable, which is not the case in many situations – mhnatiuk Apr 21 '21 at 13:09
  • Since 2014: pip install switch –  May 27 '21 at 03:26
  • 2
    `Python version 3.8 does not support match statements` – TarangP Apr 06 '22 at 06:32
  • 1
    This was about time! :) – HelloGoodbye Jul 19 '22 at 16:33
  • just started with Python. Never in 1m years did i think a 30yr old language wouldn't have this feature. Guess I have to upgrade :(:( – java-addict301 Oct 03 '22 at 19:05
1581

If you'd like defaults, you could use the dictionary get(key[, default]) function:

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 will be returned default if x is not found
Nick
  • 27,566
  • 12
  • 60
  • 72
  • 13
    What if 'a' and 'b' match 1, and 'c' and 'd' match 2? – John Mee Apr 09 '10 at 07:57
  • 14
    @JM: Well, obviously dictionary lookups don't support fall-throughs. You could do a double dictionary lookup. I.e. 'a' & 'b' point to answer1 and 'c' and 'd' point to answer2, which are contained in a second dictionary. – Nick Apr 09 '10 at 09:54
  • 3
    this is better to pass a default value – HaTiMSuM Oct 13 '16 at 11:01
  • 1
    There is a problems with this approach, first each time you call f you going to create the dict again second if you have more complex value you can get an exceptions ex. if x is a tuple and we are want to do something like this x = ('a') def f(x): return { 'a': x[0], 'b': x[1] }.get(x[0], 9) This will raise IndexError – Idan Haim Shalom Aug 30 '17 at 08:01
  • 4
    @Idan: The question was to replicate switch. I'm sure I could break this code too if I tried putting in odd values. Yes, it will recreate, but it is simple to fix. – Nick Sep 19 '17 at 21:59
  • Before Python 3.10 this should have been the most upvoted answer – FLAK-ZOSO Jan 23 '22 at 14:32
485

I've always liked doing it this way

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

From here

Michelle Welcks
  • 3,513
  • 4
  • 21
  • 34
Mark Biek
  • 146,731
  • 54
  • 156
  • 201
  • 6
    He's asking for fixed values. Why generate a function to calculate something when it's a lookup? Interesting solution for other problems though. – Nick Jan 21 '10 at 17:06
  • 37
    it maybe isn't a good idea to use lambda in this case because lambda is actually called each time the dictionary is built. – Asher Apr 22 '12 at 21:48
  • 16
    Sadly this is the closest people are going to get. Methods which use `.get()` (like the current highest answers) will need to eagerly evaluate all possibilities before dispatching, and therefore not only are (not just very but) extremely inefficient and also cannot have side-effects; this answer gets around that issue, but is more verbose. I would just use if/elif/else, and even those take just as long to write as 'case'. – ninjagecko Mar 17 '14 at 13:48
  • 14
    wouldn't this evaluate all the functions/lambdas every time in all cases, even if it is only returning one of the results? – slf Aug 06 '14 at 19:04
  • What if you need case fall through ? – Michael M Aug 07 '14 at 23:39
  • only problem with this method is when you try to store functions you later want to reference properties. – hamsolo474 - Reinstate Monica Apr 05 '15 at 06:43
  • 27
    @slf No, when the control flow reaches that piece of code, it will build 3 functions (through the use of the 3 lambdas) and then build a dictionary with those 3 functions as values, but they remain uncalled (_evaluate_ is slightly ambiguous in that context) at first. Then the dictionary gets indexed via `[value]`, which will return only one of the 3 functions (assuming `value` is one of the 3 keys). The function hasn't been called at that point, yet. Then `(x)` calls the just returned function with `x` as argument (and the result goes to `result`). The other 2 functions won't be called. – blubberdiblub Sep 18 '15 at 06:11
  • 5
    @blubberdiblub ah, so it will create all the functions every time, but only evaluate one of them – slf Sep 18 '15 at 11:44
  • @slf Yes, that's exactly what happens. I wish I would've been able to put it as short as you did ;) – blubberdiblub Sep 18 '15 at 13:09
  • Not for fixed values, but for costly (time, memory etc) functions this is preferred as the lambdas will not be evaluated during construction of the dictionary. +1 to @blubberdiblub 's comment for clarifying this. – dojuba May 22 '18 at 14:50
  • @ninjagecko why do you think using `get` forces any dictionary to evaluate all of the values - it doesn't. `get` looks for the key - which is a O(1) result and then if the key doesn't exist will then return the default - it is no worse than : if key in dict: return dict[key] else: return default – Tony Suffolk 66 Jun 04 '18 at 12:22
  • 1
    @TonySuffolk66: You misunderstand my phrasing; there's no such thing as a dict re-evaluating its values (which are already evaled in non-lazy python). I just said that answers which construct a dictionary and call .get --to directly return the value-- must have already evaled those values. You would not write `{True:f(), False:g()}[x]` (O(f+g)) instead of `f() if x else g()` (O(max(f,g))). The only grace is OP asks for fixed values, but this proves one can't replace a switch statement with a `{...}`/`.get` sans lambda if one cares about asymptotics. Of course function call overhead is bad too. – ninjagecko Jun 05 '18 at 13:47
  • @ninjagecko - I understand what you are saying - it was just that your comment suggested (well the way I read it) that using 'get' caused extra problem - and I was puzzled. – Tony Suffolk 66 Jun 06 '18 at 16:34
  • 1
    You only need to build that dictionary **once**: `result = { }` and then call it with `result[val](x)`. This then becomes a very efficient solution. – Dave Rove Sep 15 '19 at 07:10
  • Is it possible to have the lambdas with various parameters? I.e. case 'a' could have `lambda x: 2*x`, case 'b' have `lambda x, y: x + y`, case 'c' have `lambda y: 2*y`... – abrac Dec 12 '20 at 20:09
461

In addition to the dictionary methods (which I really like, BTW), you can also use if-elif-else to obtain the switch/case/default functionality:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

This of course is not identical to switch/case - you cannot have fall-through as easily as leaving off the break statement, but you can have a more complicated test. Its formatting is nicer than a series of nested ifs, even though functionally that's what it is closer to.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Matthew Schinckel
  • 35,041
  • 6
  • 86
  • 121
  • 81
    i'd really prefer this, it uses a standart language construct and doesn't throw a KeyError if no matching case is found – martyglaubitz May 18 '13 at 10:30
  • 9
    I thought about the dictionary / [`get`](https://docs.python.org/3/library/stdtypes.html#dict.get) way, but the standard way is simply more readable. – Martin Thoma Jun 25 '15 at 06:33
  • Doesn't exactly operate like a switch statement but very close. In my opinion closest thing – Arijoon Aug 26 '15 at 20:37
  • This is the cleanest solution. It's most common that each `switch` has a `break` and the most common use for fall-through that I see is to match multiple elements, as in `if x in 'bc':`. – bmacnaughton Dec 30 '15 at 16:32
  • But the variable 'x' is repeated multiple times. If you need to replace x, it is a lot easier with a switch/case statement. Also, if/elif/else gives you too much freedom (as seen in example above, mixing `==` and `in`, conditions could overlap and that becomes hard to read. – some user Feb 29 '16 at 18:58
  • 2
    @someuser but the fact they can "overlap" is a feature. You just make sure the order is the priority in which matches should occur. As for repeated x: just do an `x = the.other.thing` before. Typically, you'd have a single if, multiple elif and a single else, as that's easier to understand. – Matthew Schinckel Mar 03 '16 at 06:55
  • Also, it needn't have the same _type_ of test applied to x each time. i.e., == vs in vs not in or whatever. – Matthew Schinckel Mar 03 '16 at 06:57
  • 1
    @Matthew: Simply put, if you need that kind of flexibility, you use `if/elif/else`. But most who visit this topic don't. We want more structural code. Any language that supports `switch` already supports `if/elif/else`. – some user Mar 03 '16 at 21:33
  • 8
    Nice, the "Fall-through by not using elif " is a bit confusing, though. What about this: forget about "fall through" and just accept it as two `if/elif/else`'s? – Alois Mahdal May 30 '16 at 11:40
  • 10
    Also worth mentioning, when using things like `x in 'bc'`, keep in mind that `"" in "bc"` is `True`. – Lohmar ASHAR Aug 28 '18 at 11:46
  • 1
    This really is the answer. Sure you can create a dictionary construct, but the `if/elif` is the most clear. – Armstrongest Apr 01 '19 at 21:33
  • 1
    This is probably the most fragile way to design this construct, especially using fallthroughs in this manner which is nonintuitive and doubles code. – user-63873687 May 25 '20 at 11:36
  • 1
    @user-63873687 Yeah, I didn't write the fall-through code (which is really fragile, I agree). I wonder if there is a way to take my name off the answer, because I really don't like that pattern: I mis-read it myself just now. – Matthew Schinckel May 25 '20 at 23:28
  • Seems more conventional than everything else so far. Dictionaries add flexibility of choice and reuse, but for small cases make no sense. – vintprox Jun 14 '20 at 07:32
  • It also worth mentioning that you can write `if x = 'a': action = 'foo'; result = fn(action)` on a single line without needing a newline and indent. This can make if else blocks more concise and more closely resemble a case statement. – James McGuigan Dec 22 '20 at 00:18
  • It has many advantages but the key disadvantage that the 'compiled' code not optimized (and Typed). But fall-through is not a recommended coding style and not allowed by some languages, like C#. A way to implement that is to wrap in a while(true){break} pattern: while(True): if x == 'a': # Do something and fall through if x == 'b': # Do something break; if x == c: # etc. else: # Default break EDIT: new to site - gottta figure out how to paste code... – Guidobot Nov 17 '21 at 21:36
  • Why does anyone want anything more complicated than this? It's both punchier than C's switch statement by not having all those `break;` clauses and more flexible in that you can use arbitrary conditionals instead of comparing to constant values. – pullmyteeth Aug 08 '22 at 20:50
  • @pullmyteeth What I like about a switch statement is lets me know with absolute certainty when the conditionals are *not* arbitrary. When I see `switch(x)`, I immediately know that all the "conditionals" will compare `x` to something. With `if/elif/else`, I have to scan through it to make sure it's not `if x == 'a'/elif y == 'b'/else`. It doesn't take long to know that if its not the case but with the switch, it would be impossible. Having said that, I do think that `if/elif/else` is way better than all those complicated things. – Philippe Carphin Jun 30 '23 at 19:28
379

Python >= 3.10

Wow, Python 3.10+ now has a match/case syntax which is like switch/case and more!

PEP 634 -- Structural Pattern Matching

Selected features of match/case

1 - Match values:

Matching values is similar to a simple switch/case in another language:

match something:
    case 1 | 2 | 3:
        # Match 1-3.
    case _:
        # Anything else.
        # 
        # If `case _:` is omitted, an error will be thrown
        # if `something` doesn't match any of the patterns.

2 - Match structural patterns:

match something:
    case str() | bytes():  
        # Match a string like object.
    case [str(), int()]:
        # Match a `str` and an `int` sequence 
        # (A sequence can be a `list` or a `tuple` but not a `set` or an iterator). 
    case [_, _]:
        # Match a sequence of 2 variables.
        # To prevent a common mistake, sequence patterns don’t match strings.
    case {"bandwidth": 100, "latency": 300}:
        # Match this dict. Extra keys are ignored.

3 - Capture variables

Parse an object; saving it as variables:

match something:
    case [name, count]
        # Match a sequence of any two objects and parse them into the two variables.
    case [x, y, *rest]:
        # Match a sequence of two or more objects, 
        # binding object #3 and on into the rest variable.
    case bytes() | str() as text:
        # Match any string like object and save it to the text variable.

Capture variables can be useful when parsing data (such as JSON or HTML) that may come in one of a number of different patterns.

Capture variables is a feature. But it also means that you need to use dotted constants (ex: COLOR.RED) only. Otherwise, the constant will be treated as a capture variable and overwritten.

More sample usage:

match something:
    case 0 | 1 | 2:
        # Matches 0, 1 or 2 (value).
        print("Small number")
    case [] | [_]:
        # Matches an empty or single value sequence (structure).
        # Matches lists and tuples but not sets.
        print("A short sequence")
    case str() | bytes():
        # Something of `str` or `bytes` type (data type).
        print("Something string-like")
    case _:
        # Anything not matched by the above.
        print("Something else")

Python <= 3.9

My favorite Python recipe for switch/case was:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Short and simple for simple scenarios.

Compare to 11+ lines of C code:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

You can even assign multiple variables by using tuples:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
ChaimG
  • 7,024
  • 4
  • 38
  • 46
  • 36
    I find this to be a more robust answer than the accepted. – cerd Aug 18 '15 at 23:16
  • 1
    Note that the 2 segments of code aren't the same. The python version returns 'default' when no match but the C version returns -1. When you replace the line as `choices.get(key, -1)`, it becomes harder to read because people have to pause to think how get() works and what `-1` mean. – some user Feb 29 '16 at 19:05
  • 3
    @some user: C requires that the return value be the same type for all cases. Python does not. I wanted to highlight this flexibility of Python just in case someone had a situation that warranted such usage. – ChaimG Mar 01 '16 at 00:17
  • 4
    @some user: Personally, I find {}.get(,) readable. For extra readability for Python beginners you may want to use `default = -1; result = choices.get(key, default)`. – ChaimG Mar 01 '16 at 00:19
  • 9
    compare with 1 line of c++ `result=key=='a'?1:key==b?2:-1` – Jasen Aug 23 '16 at 22:01
  • 5
    @Jasen one can argue that you can do it in one line of Python as well: `result = 1 if key == 'a' else (2 if key == 'b' else 'default')`. but is the one liner readable? – ChaimG Aug 24 '16 at 02:57
  • 2
    @ChaimG tbh C one liner is fairly readeable under these simple cases – Valen Jun 06 '17 at 11:22
  • 1
    An additional benefit of this approach is that the code and the data are more clearly separated. This make it easier to pass the data as a parameter (simplifies the task of writing tests) or move it into a configuration file, for example. – Peter Sep 11 '17 at 15:37
  • Such is the trouble with comparing languages by how many lines it takes to express an idea. You can usually accomplish something in Python with one line, and you can *always* express something with one line in C. – Rakurai Jan 06 '20 at 14:51
  • note that you are missing features that are considered inherent in switch statements, such as fallthrough – user-63873687 May 25 '20 at 11:38
  • does this approach allocate memory for dictionary every time? – Pranav Sep 01 '20 at 01:37
  • This question is purely about Python, not about Python and C (or C++) comparison. Every language has its own advantages and disadvantages. Almost any statement can fail to execute at runtime if proper type checking isn't done, while statically typed languages as C++ detects them at compile time. On the other hand, Python is with batteries included, with a huge amount of modules. Can you say any of the languages as the best? – Akib Azmain Turja Jan 11 '21 at 10:30
  • Choice is not equivalent in that all the value expressions are evaluated. This can result in an exception if an expression is dependent on the key, as well as poor performance. The equivalent is if/elif/else. – Chris Mar 22 '21 at 11:04
118
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Usage:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Tests:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
KurzedMetal
  • 12,540
  • 6
  • 39
  • 65
adamh
  • 1,293
  • 1
  • 8
  • 3
  • 70
    This is not threat safe. If several switches are hit at the same time all switches take the value of the last switch. – francescortiz Jun 26 '13 at 16:35
  • 58
    While @francescortiz likely means thread safe, it's also not threat safe. It threatens the values of the variables! – Zizouz212 Jun 16 '15 at 16:29
  • 7
    The thread safety problem could likely be worked around by using [thread-local storage](https://docs.python.org/2/library/threading.html#threading.local). Or it could be avoided altogether by returning an instance and using that instance for the case comparisons. – blubberdiblub Sep 18 '15 at 06:24
  • 6
    @blubberdiblub But then isn't it just more efficient to use a standard `if` statement? – wizzwizz4 May 23 '16 at 17:32
  • 2
    @wizzwizz4 switch could return a function: `case=switch( foo )` if is more efficient than the above (or do you mean less typing) – Jasen Aug 23 '16 at 21:59
  • @Jasen I think I meant typing, because we already know that `if` is going to be faster. – wizzwizz4 Aug 24 '16 at 09:02
  • It seems the normal fall-through behavior of switch statements does not happen here (unless the prior cases are explicitly specified, as in the example), which might be somewhat misleading. – Grayscale Aug 02 '17 at 18:53
  • 10
    This is also not safe if used in multiple functions. In the example given, if the `case(2)` block called another function that uses switch(), then when doing `case(2, 3, 5, 7)` etc to look for the next case to execute, it will use the switch value set by the other function not the one set by the current switch statement. – user9876 Aug 17 '17 at 08:58
  • ‐​­1, this is neither thread safe nor can it be called recursively. I seriously hope you guys don't do this. – Navin Jul 11 '18 at 19:22
  • 2
    Using a module-global variable to track local state like this is a terrible idea. What happens when some switch case logic calls into a function which also uses `switch`? The `switch.value` will be overwritten, leading to broken handling of subsequent cases! [There is a much better implementation of this approach below](https://stackoverflow.com/a/30012053/1941213), and people should downvote this answer, and upvote that one. – Alex Coventry Sep 14 '18 at 15:41
  • Fallthrough could be more cleanly implemented by using a fallthrough variable in your class, which is set upon calling switch() and can set the next call to true. Eg: "select.fallthrough = select.fallthrough or any((arg == switch.value for arg in args))" "return select.fallthrough" Then when testing case(2, 3, 5, 7) you can skip out the "2" and it will still trigger. – user-63873687 May 25 '20 at 11:42
  • This method is also inefficient. There are two nested loops, and dictionaries would be better than the for loop. – Joël V. Jun 19 '20 at 15:22
67

My favorite one is a really nice recipe. It's the closest one I've seen to actual switch case statements, especially in features.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration
    
    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

Here's an example:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"

Some of the comments indicated that a context manager solution using with foo as case rather than for case in foo might be cleaner, and for large switch statements the linear rather than quadratic behavior might be a nice touch. Part of the value in this answer with a for loop is the ability to have breaks and fallthrough, and if we're willing to play with our choice of keywords a little bit we can get that in a context manager too:

class Switch:
    def __init__(self, value):
        self.value = value
        self._entered = False
        self._broken = False
        self._prev = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        if self._broken:
            return False
        
        if not self._entered:
            if values and self.value not in values:
                return False
            self._entered, self._prev = True, values
            return True
        
        if self._prev is None:
            self._prev = values
            return True
        
        if self._prev != values:
            self._broken = True
            return False
        
        if self._prev == values:
            self._prev = None
            return False
    
    @property
    def default(self):
        return self()

Here's an example:

# Prints 'bar' then 'baz'.
with Switch(2) as case:
    while case(0):
        print('foo')
    while case(1, 2, 3):
        print('bar')
    while case(4, 5):
        print('baz')
        break
    while case.default:
        print('default')
        break
desertnaut
  • 57,590
  • 26
  • 140
  • 166
John Doe
  • 3,436
  • 1
  • 18
  • 21
  • 3
    I would substitute `for case in switch()` with `with switch() as case`, makes more sense, since it need s to run only once. – Ski Dec 12 '13 at 16:24
  • 5
    @Skirmantas: Note that ``with`` doesn’t allow for ``break`` though, so the fallthrough option is taken away. – Jonas Schäfer May 08 '14 at 16:53
  • 5
    Apologies for not putting more effort in to determine this myself: a similar answer above is not thread safe. Is this? – David Winiecki Sep 12 '14 at 15:47
  • 1
    @DavidWiniecki The code components missing from the above (and possibly copyright by activestate) appear to be thread safe. – Jasen Aug 23 '16 at 22:09
  • would another version of this be something like `if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"`? – mpag Oct 31 '16 at 19:08
66
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


from datetime import datetime

with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4):
        print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes
Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
Ian Bell
  • 661
  • 5
  • 2
  • 13
    Using context managers is a good creative solution. I'd recommend adding a bit of explanation and maybe a link to some information on Context Managers to give this post some, well, context ;) – Will May 03 '15 at 09:13
  • 2
    I don't like if/elif chains much but this is both the most creative and the most practical of all the solutions I've seen using Python's existing syntax. – itsbruce Oct 02 '17 at 08:05
  • 2
    This is really nice. One suggested improvement is to add a (public) `value` property to the Switch class so that you can reference the `case.value` within the statement. – Peter Jan 24 '19 at 11:03
  • This answer provides the most switch like functionality while been quite simple. The problem with using a `dict` is that you can only retrieve data and can't run functions/methods. – moshevi Oct 05 '20 at 15:55
52

There's a pattern that I learned from Twisted Python code.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

You can use it any time you need to dispatch on a token and execute extended piece of code. In a state machine you would have state_ methods, and dispatch on self.state. This switch can be cleanly extended by inheriting from base class and defining your own do_ methods. Often times you won't even have do_ methods in the base class.

Edit: how exactly is that used

In case of SMTP you will receive HELO from the wire. The relevant code (from twisted/mail/smtp.py, modified for our case) looks like this

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

You'll receive ' HELO foo.bar.com ' (or you might get 'QUIT' or 'RCPT TO: foo'). This is tokenized into parts as ['HELO', 'foo.bar.com']. The actual method lookup name is taken from parts[0].

(The original method is also called state_COMMAND, because it uses the same pattern to implement a state machine, i.e. getattr(self, 'state_' + self.mode))

  • 4
    I don't see the benefit from this pattern over just calling the methods directly: SMTP().do_HELO('foo.bar.com') OK, there can be common code in the lookupMethod, but since that also can be overwritten by the subclass I don't see what you gain from the indirection. – Mr Shark Sep 13 '08 at 11:35
  • 1
    You wouldn't know what method to call in advance, that is to say 'HELO' comes from a variable. i've added usage example to the original post –  Sep 13 '08 at 17:45
  • May I suggest simply: eval('SMTP().do_' + command)('foo.bar.com') – jforberg Jun 21 '11 at 17:32
  • Also, why instantiate a new SMTP object for each method call? That's what global functions are for. – jforberg Jun 21 '11 at 17:34
  • 8
    eval? seriously? and instead of instantiating one method per call, we can very well instantiate once and use it in all calls provided it has no internal state. – Mahesh Mar 19 '13 at 18:10
  • 1
    IMO the real key here is the dispatching using getattr to specify a function to run. If the methods were in a module, you could do getattr(locals(), func_name) to get it. The 'do_' part is good for security/errors so only funcs with the prefix can be called. SMTP itself calls lookupMethod. Ideally the outside doesn't know about any of this. It doesn't really make sense to do SMTP().lookupMethod(name)(data). Since the command and data are in one string and SMTP parses it, that makes more sense. Lastly, SMTP probably has other shared state which justifies it being a class. – ShawnFumo Aug 15 '13 at 22:17
  • This method is unsettling. Really, just use a `dict`. No need for this fancy `getattr` nonsense. – miike3459 May 17 '19 at 10:30
31

I'm just going to drop my two cents in here. The reason there isn't a case/switch statement in Python is because Python follows the principle of "there's only one right way to do something". So obviously you could come up with various ways of recreating switch/case functionality, but the Pythonic way of accomplishing this is the if/elif construct. I.e.,

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

I just felt PEP 8 deserved a nod here. One of the beautiful things about Python is its simplicity and elegance. That is largely derived from principles laid out in PEP 8, including "There's only one right way to do something."

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user2233949
  • 2,053
  • 19
  • 22
30

Let's say you don't want to just return a value, but want to use methods that change something on an object. Using the approach stated here would be:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

Here Python evaluates all methods in the dictionary.

So even if your value is 'a', the object will get incremented and decremented by x.

Solution:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

So you get a list containing a function and its arguments. This way, only the function pointer and the argument list get returned, not evaluated. 'result' then evaluates the returned function call.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GeeF
  • 707
  • 2
  • 8
  • 16
30

Solution to run functions:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
}.get(option)(parameters_optional)

where foo1(), foo2() and foo3() are functions

Example 1 (with parameters):

option = number['type']
result = {
    'number':     value_of_int,  # result = value_of_int(number['value'])
    'text':       value_of_text, # result = value_of_text(number['value'])
    'binary':     value_of_bin,  # result = value_of_bin(number['value'])
}.get(option)(value['value'])

Example 2 (no parameters):

option = number['type']
result = {
    'number':     func_for_number, # result = func_for_number()
    'text':       func_for_text,   # result = func_for_text()
    'binary':     func_for_bin,    # result = func_for_bin()
}.get(option)()

Example 4 (only values):

option = number['type']
result = {
    'number':    lambda: 10,       # result = 10
    'text':      lambda: 'ten',    # result = 'ten'
    'binary':    lambda: 0b101111, # result = 47
}.get(option)()
24

If you have a complicated case block you can consider using a function dictionary lookup table...

If you haven't done this before it's a good idea to step into your debugger and view exactly how the dictionary looks up each function.

NOTE: Do not use "()" inside the case/dictionary lookup or it will call each of your functions as the dictionary / case block is created. Remember this because you only want to call each function once using a hash style lookup.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Asher
  • 2,638
  • 6
  • 31
  • 41
21

If you're searching extra-statement, as "switch", I built a Python module that extends Python. It's called ESPY as "Enhanced Structure for Python" and it's available for both Python 2.x and Python 3.x.

For example, in this case, a switch statement could be performed by the following code:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

That can be used like this:

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

So espy translate it in Python as:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
elp
  • 211
  • 2
  • 2
  • Very cool, but what's the point of the `while True:` at the top of the generated Python code? It'll inevitably hit the `break` at the bottom of the generated Python code, so it seems to me that both the `while True:` and `break` could be removed. Further, is ESPY smart enough to change the name of `cont` if the user uses that same name in their own code? In any event, I want to use vanilla Python so I won't use this, but it's cool none-the-less. +1 for sheer coolness. – ArtOfWarfare Jun 29 '14 at 12:56
  • @ArtOfWarfare The reason for the `while True:` and `break`s is to allow but not require fall-through. – Solomon Ucko Mar 23 '19 at 14:10
  • Is this module still available? – Solomon Ucko Mar 24 '19 at 00:57
19

Expanding on the "dict as switch" idea. If you want to use a default value for your switch:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jeremy Cantrell
  • 26,392
  • 13
  • 55
  • 78
  • 19
    I think it's clearer to use .get() on the dict with the default specified. I prefer to leave Exceptions for exceptional circumstances, and it cuts three lines of code and a level of indentation without being obscure. – Chris B. Jun 05 '09 at 15:14
  • 10
    This **is** an exceptional circumstance. It may or may not be a *rare* circumstance depending on useful, but it's definitely an exception (fall back on `'default'`) from the rule (get something from this dict). By design, Python programs use exceptions at the drop of a hat. That being said, using `get` could potentially make the code a bit nicer. – Mike Graham Mar 26 '10 at 16:49
19

Most of the answers here are pretty old, and especially the accepted ones, so it seems worth updating.

First, the official Python FAQ covers this, and recommends the elif chain for simple cases and the dict for larger or more complex cases. It also suggests a set of visit_ methods (a style used by many server frameworks) for some cases:

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

The FAQ also mentions PEP 275, which was written to get an official once-and-for-all decision on adding C-style switch statements. But that PEP was actually deferred to Python 3, and it was only officially rejected as a separate proposal, PEP 3103. The answer was, of course, no—but the two PEPs have links to additional information if you're interested in the reasons or the history.


One thing that came up multiple times (and can be seen in PEP 275, even though it was cut out as an actual recommendation) is that if you're really bothered by having 8 lines of code to handle 4 cases, vs. the 6 lines you'd have in C or Bash, you can always write this:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

This isn't exactly encouraged by PEP 8, but it's readable and not too unidiomatic.


Over the more than a decade since PEP 3103 was rejected, the issue of C-style case statements, or even the slightly more powerful version in Go, has been considered dead; whenever anyone brings it up on python-ideas or -dev, they're referred to the old decision.

However, the idea of full ML-style pattern matching arises every few years, especially since languages like Swift and Rust have adopted it. The problem is that it's hard to get much use out of pattern matching without algebraic data types. While Guido has been sympathetic to the idea, nobody's come up with a proposal that fits into Python very well. (You can read my 2014 strawman for an example.) This could change with dataclass in 3.7 and some sporadic proposals for a more powerful enum to handle sum types, or with various proposals for different kinds of statement-local bindings (like PEP 3150, or the set of proposals currently being discussed on -ideas). But so far, it hasn't.

There are also occasionally proposals for Perl 6-style matching, which is basically a mishmash of everything from elif to regex to single-dispatch type-switching.

abarnert
  • 354,177
  • 51
  • 601
  • 671
18

I found that a common switch structure:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

can be expressed in Python as follows:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

or formatted in a clearer way:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

Instead of being a statement, the Python version is an expression, which evaluates to a value.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
leo
  • 360
  • 2
  • 7
  • Also instead of ...parameter... and p1(x) how about `parameter` and `p1==parameter` – Bob Stein Mar 11 '15 at 15:28
  • @BobStein-VisiBone hi, here is an example that runs in my python session: `f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'`. When I later called `f(2)`, I got `'c'`; `f(1)`, `'b'`; and `f(0)`, `'a'`. As for p1(x), it denotes a predicate; as long as it returns `True` or `False`, no matter it is a function call or a expression, it's fine. – leo Mar 13 '15 at 16:16
  • @BobStein-VisiBone Yes, you are right! Thank :) For the multi-line expression to work, parentheses should be placed, either as in your suggestion, or as in my modified example. – leo Mar 14 '15 at 05:16
  • Excellent. Now I'll [delete all my comments](http://meta.stackexchange.com/q/248179/209317) about the parens. – Bob Stein Mar 14 '15 at 20:48
16

The solutions I use:

A combination of 2 of the solutions posted here, which is relatively easy to read and supports defaults.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

where

.get('c', lambda x: x - 22)(23)

looks up "lambda x: x - 2" in the dict and uses it with x=23

.get('xxx', lambda x: x - 22)(44)

doesn't find it in the dict and uses the default "lambda x: x - 22" with x=44.

bluish
  • 26,356
  • 27
  • 122
  • 180
thomasf1
  • 1,782
  • 4
  • 18
  • 32
15

You can use a dispatched dict:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Output:

This is case 1
This is case 3
This is case 2
This is case 1
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Felix Martinez
  • 512
  • 5
  • 9
14

I didn't find the simple answer I was looking for anywhere on Google search. But I figured it out anyway. It's really quite simple. Decided to post it, and maybe prevent a few less scratches on someone else's head. The key is simply "in" and tuples. Here is the switch statement behavior with fall-through, including RANDOM fall-through.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

Provides:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
JD Graham
  • 189
  • 2
  • 3
  • Where exactly is fallthrough here? – Jonas Schäfer May 08 '14 at 16:56
  • Oops! There is fall through there, but I'm not contributing to Stack Overflow anymore. Don't like THEM at all. I like the contributions by others, but just not Stackoverflow. If you're using fall through for FUNCTIONALITY then you want to CATCH certain conditions in all in one case statement in a switch (a catch all), until you reach a break statement in a switch. – JD Graham Jul 30 '14 at 04:58
  • 2
    Here both the values "Dog" and "Cat" FALL THROUGH and are handled by the SAME functionality, which is they are defined as having "four legs." It's an ABSTRACT equivalent to fall through and different values handled by the SAME case statement where a break occurs. – JD Graham Jul 30 '14 at 05:16
  • @JDGraham I think Jonas meant another aspect of fallthrough, which happens when programmer occasionally forget to write `break` in the end of the code for a `case`. But I think we don't need _such_ "fallthrough" :) – Mikhail Batcer Aug 12 '15 at 09:00
14

I was quite confused after reading the accepted answer, but this cleared it all up:

def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")

This code is analogous to:

function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}

Check the Source for more about dictionary mapping to functions.

Yster
  • 3,147
  • 5
  • 32
  • 48
13
# simple case alternative

some_value = 5.0

# this while loop block simulates a case block

# case
while True:

    # case 1
    if some_value > 5:
        print ('Greater than five')
        break

    # case 2
    if some_value == 5:
        print ('Equal to five')
        break

    # else case 3
    print ( 'Must be less than 5')
    break
pbfy0
  • 616
  • 4
  • 13
user5224656
  • 139
  • 1
  • 2
12
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary
Vikhyat Agarwal
  • 1,723
  • 1
  • 11
  • 29
10

I liked Mark Bies's answer

Since the x variable must used twice, I modified the lambda functions to parameterless.

I have to run with results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Edit: I noticed that I can use None type with with dictionaries. So this would emulate switch ; case else

Community
  • 1
  • 1
guneysus
  • 6,203
  • 2
  • 45
  • 47
9
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Short and easy to read, has a default value and supports expressions in both conditions and return values.

However, it is less efficient than the solution with a dictionary. For example, Python has to scan through all the conditions before returning the default value.

emu
  • 1,597
  • 16
  • 20
9

Simple, not tested; each condition is evaluated independently: there is no fall-through, but all cases are evaluated (although the expression to switch on is only evaluated once), unless there is a break statement. For example,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

prints Was 1. Was 1 or 2. Was something. (Dammit! Why can't I have trailing whitespace in inline code blocks?) if expression evaluates to 1, Was 2. if expression evaluates to 2, or Was something. if expression evaluates to something else.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
7

A solution I tend to use which also makes use of dictionaries is:

def decision_time( key, *args, **kwargs):
    def action1()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action2()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action3()
        """This function is a closure - and has access to all the arguments"""
        pass

   return {1:action1, 2:action2, 3:action3}.get(key,default)()

This has the advantage that it doesn't try to evaluate the functions every time, and you just have to ensure that the outer function gets all the information that the inner functions need.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tony Suffolk 66
  • 9,358
  • 3
  • 30
  • 33
7

There have been a lot of answers so far that have said, "we don't have a switch in Python, do it this way". However, I would like to point out that the switch statement itself is an easily-abused construct that can and should be avoided in most cases because they promote lazy programming. Case in point:

def ToUpper(lcChar):
    if (lcChar == 'a' or lcChar == 'A'):
        return 'A'
    elif (lcChar == 'b' or lcChar == 'B'):
        return 'B'
    ...
    elif (lcChar == 'z' or lcChar == 'Z'):
        return 'Z'
    else:
        return None        # or something

Now, you could do this with a switch-statement (if Python offered one) but you'd be wasting your time because there are methods that do this just fine. Or maybe, you have something less obvious:

def ConvertToReason(code):
    if (code == 200):
        return 'Okay'
    elif (code == 400):
        return 'Bad Request'
    elif (code == 404):
        return 'Not Found'
    else:
        return None

However, this sort of operation can and should be handled with a dictionary because it will be faster, less complex, less prone to error and more compact.

And the vast majority of "use cases" for switch statements will fall into one of these two cases; there's just very little reason to use one if you've thought about your problem thoroughly.

So, rather than asking "how do I switch in Python?", perhaps we should ask, "why do I want to switch in Python?" because that's often the more interesting question and will often expose flaws in the design of whatever you're building.

Now, that isn't to say that switches should never be used either. State machines, lexers, parsers and automata all use them to some degree and, in general, when you start from a symmetrical input and go to an asymmetrical output they can be useful; you just need to make sure that you don't use the switch as a hammer because you see a bunch of nails in your code.

Woody1193
  • 7,252
  • 5
  • 40
  • 90
6

Defining:

def switch1(value, options):
  if value in options:
    options[value]()

allows you to use a fairly straightforward syntax, with the cases bundled into a map:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

I kept trying to redefine switch in a way that would let me get rid of the "lambda:", but gave up. Tweaking the definition:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

Allowed me to map multiple cases to the same code, and to supply a default option:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

Each replicated case has to be in its own dictionary; switch() consolidates the dictionaries before looking up the value. It's still uglier than I'd like, but it has the basic efficiency of using a hashed lookup on the expression, rather than a loop through all the keys.

6

I think the best way is to use the Python language idioms to keep your code testable. As showed in previous answers, I use dictionaries to take advantage of python structures and language and keep the "case" code isolated in different methods. Below there is a class, but you can use directly a module, globals and functions. The class has methods that can be tested with isolation.

Depending to your needs, you can play with static methods and attributes too.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

It is possible to take advantage of this method using also classes as keys of "__choice_table". In this way you can avoid isinstance abuse and keep all clean and testable.

Supposing you have to process a lot of messages or packets from the net or your MQ. Every packet has its own structure and its management code (in a generic way).

With the above code it is possible to do something like this:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

So complexity is not spread in the code flow, but it is rendered in the code structure.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
J_Zar
  • 2,034
  • 2
  • 21
  • 34
  • Really ugly... switch case is so clean when reading. Cannot understand why it is not implemented in Python. – jmcollin92 Apr 08 '16 at 13:25
  • @AndyClifton: I'm sorry... an example? Think of every time you need to have multiple decision branching code and you can apply this method. – J_Zar Apr 11 '16 at 06:51
  • @jmcollin92: the switch statement is confortable, I agree. However the programmer tends to write very long statements and code which is not reusable. The way I described is cleaner to test and more reusable, IMHO. – J_Zar Apr 11 '16 at 06:53
  • @J_Zar : re. My request for an example: yes, I get that, but I'm struggling to put this into the context of a larger piece of code. Could you show how I might use this in a real world situation? – Andy Clifton Apr 11 '16 at 09:14
  • 1
    @AndyClifton: I'm sorry, I'm late but I posted some example case. – J_Zar Apr 20 '16 at 14:56
6

Expanding on Greg Hewgill's answer - We can encapsulate the dictionary-solution using a decorator:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

This can then be used with the @case-decorator

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

The good news are that this has already been done in NeoPySwitch-module. Simply install using pip:

pip install NeoPySwitch
Community
  • 1
  • 1
Tom
  • 414
  • 4
  • 17
4

Just mapping some a key to some code is not really an issue as most people have shown using the dict. The real trick is trying to emulate the whole drop through and break thing. I don't think I've ever written a case statement where I used that "feature". Here's a go at drop through.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

I was really imagining it as a single statement. I hope you'll pardon the silly formatting.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

I hope that's valid Python code. It should give you drop through. Of course the Boolean checks could be expressions and if you wanted them to be evaluated lazily you could wrap them all in a lambda. It wouldn't be to hard to make it accept after executing some of the items in the list either. Just make the tuple (bool, bool, function) where the second bool indicates whether or not to break or drop through.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alden
  • 312
  • 5
  • 13
  • 1
    Drop-through is unstructured. You don't need it, it will make your code harder to maintain, harder to avoid bugs, and is banned my many coding standards. It is a hangover from C. C did not go 100% structured, it left in drop-through, goto, continue, break. Most of its descendent copied it. – ctrl-alt-delor Feb 11 '13 at 10:34
4

If you don't worry losing syntax highlight inside the case suites, you can do the following:

exec {
    1: """
print ('one')
""", 
    2: """
print ('two')
""", 
    3: """
print ('three')
""",
}.get(value, """
print ('None')
""")

Where value is the value. In C, this would be:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

We can also create a helper function to do this:

def switch(value, cases, default):
    exec cases.get(value, default)

So we can use it like this for the example with one, two and three:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")
dccsillag
  • 919
  • 4
  • 14
  • 25
4

I've found the following answer from Python documentation most helpful:

You can do this easily enough with a sequence of if... elif... elif... else. There have been some proposals for switch statement syntax, but there is no consensus (yet) on whether and how to do range tests. See PEP 275 for complete details and the current status.

For cases where you need to choose from a very large number of possibilities, you can create a dictionary mapping case values to functions to call. For example:

def function_1(...):
    ...

functions = {'a': function_1,
             'b': function_2,
             'c': self.method_1, ...}

func = functions[value]
func()

For calling methods on objects, you can simplify yet further by using the getattr() built-in to retrieve methods with a particular name:

def visit_a(self, ...):
    ...
...

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

It’s suggested that you use a prefix for the method names, such as visit_ in this example. Without such a prefix, if values are coming from an untrusted source, an attacker would be able to call any method on your object.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
True
  • 419
  • 3
  • 7
4

As a minor variation on Mark Biek's answer, for uncommon cases like this duplicate where the user has a bunch of function calls to delay with arguments to pack in (and it isn't worth building a bunch of functions out-of-line), instead of this:

d = {
    "a1": lambda: a(1),
    "a2": lambda: a(2),
    "b": lambda: b("foo"),
    "c": lambda: c(),
    "z": lambda: z("bar", 25),
    }
return d[string]()

… you can do this:

d = {
    "a1": (a, 1),
    "a2": (a, 2),
    "b": (b, "foo"),
    "c": (c,)
    "z": (z, "bar", 25),
    }
func, *args = d[string]
return func(*args)

This is certainly shorter, but whether it's more readable is an open question…


I think it might be more readable (although not briefer) to switch from lambda to partial for this particular use:

d = {
    "a1": partial(a, 1),
    "a2": partial(a, 2),
    "b": partial(b, "foo"),
    "c": c,
    "z": partial(z, "bar", 25),
    }
return d[string]()

… which has the advantage of working nicely with keyword arguments as well:

d = {
    "a1": partial(a, 1),
    "a2": partial(a, 2),
    "b": partial(b, "foo"),
    "c": c,
    "k": partial(k, key=int),
    "z": partial(z, "bar", 25),
    }
return d[string]()
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • The problem with this answer is that all the arguments still get evaluated immediately. Even if this works for the user at first, if one day they have to add a case `foo(some_expensive_function(3))` then it's a problem. Either the whole thing breaks or they have to separately test for that case before the dictionary, which not only looks odd but might mislead a reader looking at a glance into thinking that the only options to consider are in the dictionary. – Alex Hall Jun 04 '18 at 19:39
  • @AlexHall When you have a hunk of arbitrary code that has to be deferred, rather than just a function call that has to be deferred, you really do need to wrap it in a function. And for anything less trivial than the toy example you just gave, it’s probably not even a lambda, but an out-of-line named def that you want. But I don’t think that’s a flaw in the language or anything, because such code really isn’t—and shouldn’t be—common. If you have a more realistic example, there’s likely a better way to refactor it. – abarnert Jun 04 '18 at 19:49
  • You're right, my example can be solved by just writing `lambda: foo(some_expensive_function(3))` for that one case. I've added [my own answer](https://stackoverflow.com/a/50688208/2482744) along these lines. – Alex Hall Jun 04 '18 at 20:24
3

I've made a switch case implementation that doesn't quite use ifs externally (it still uses an if in the class).

class SwitchCase(object):
    def __init__(self):
        self._cases = dict()

    def add_case(self,value, fn):
        self._cases[value] = fn

    def add_default_case(self,fn):
        self._cases['default']  = fn

    def switch_case(self,value):
        if value in self._cases.keys():
            return self._cases[value](value)
        else:
            return self._cases['default'](0)

Use it like this:

from switch_case import SwitchCase
switcher = SwitchCase()
switcher.add_case(1, lambda x:x+1)
switcher.add_case(2, lambda x:x+3)
switcher.add_default_case(lambda _:[1,2,3,4,5])

print switcher.switch_case(1) #2
print switcher.switch_case(2) #5
print switcher.switch_case(123) #[1, 2, 3, 4, 5]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
The Nomadic Coder
  • 580
  • 1
  • 4
  • 16
2

Although there are already enough answers, I want to point a simpler and more powerful solution:

class Switch:
    def __init__(self, switches):
        self.switches = switches
        self.between = len(switches[0]) == 3

    def __call__(self, x):
        for line in self.switches:
            if self.between:
                if line[0] <= x < line[1]:
                    return line[2]
            else:
                if line[0] == x:
                    return line[1]
        return None


if __name__ == '__main__':
    between_table = [
        (1, 4, 'between 1 and 4'),
        (4, 8, 'between 4 and 8')
    ]

    switch_between = Switch(between_table)

    print('Switch Between:')
    for i in range(0, 10):
        if switch_between(i):
            print('{} is {}'.format(i, switch_between(i)))
        else:
            print('No match for {}'.format(i))


    equals_table = [
        (1, 'One'),
        (2, 'Two'),
        (4, 'Four'),
        (5, 'Five'),
        (7, 'Seven'),
        (8, 'Eight')
    ]
    print('Switch Equals:')
    switch_equals = Switch(equals_table)
    for i in range(0, 10):
        if switch_equals(i):
            print('{} is {}'.format(i, switch_equals(i)))
        else:
            print('No match for {}'.format(i))

Output:

Switch Between:
No match for 0
1 is between 1 and 4
2 is between 1 and 4
3 is between 1 and 4
4 is between 4 and 8
5 is between 4 and 8
6 is between 4 and 8
7 is between 4 and 8
No match for 8
No match for 9

Switch Equals:
No match for 0
1 is One
2 is Two
No match for 3
4 is Four
5 is Five
No match for 6
7 is Seven
8 is Eight
No match for 9
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ramazan polat
  • 7,111
  • 1
  • 48
  • 76
2

Similar to this answer by abarnert, here is a solution specifically for the use case of calling a single function for each 'case' in the switch, while avoiding the lambda or partial for ultra-conciseness while still being able to handle keyword arguments:

class switch(object):
    NO_DEFAULT = object()

    def __init__(self, value, default=NO_DEFAULT):
        self._value = value
        self._result = default

    def __call__(self, option, func, *args, **kwargs):
        if self._value == option:
            self._result = func(*args, **kwargs)
        return self

    def pick(self):
        if self._result is switch.NO_DEFAULT:
            raise ValueError(self._value)

        return self._result

Example usage:

def add(a, b):
    return a + b

def double(x):
    return 2 * x

def foo(**kwargs):
    return kwargs

result = (
    switch(3)
    (1, add, 7, 9)
    (2, double, 5)
    (3, foo, bar=0, spam=8)
    (4, lambda: double(1 / 0))  # if evaluating arguments is not safe
).pick()

print(result)

Note that this is chaining calls, i.e. switch(3)(...)(...)(...). Don't put commas in between. It's also important to put it all in one expression, which is why I've used extra parentheses around the main call for implicit line continuation.

The above example will raise an error if you switch on a value that is not handled, e.g. switch(5)(1, ...)(2, ...)(3, ...). You can provide a default value instead, e.g. switch(5, default=-1)... returns -1.

Alex Hall
  • 34,833
  • 5
  • 57
  • 89
2

If you are really just returning a predetermined, fixed value, you could create a dictionary with all possible input indexes as the keys, along with their corresponding values. Also, you might not really want a function to do this - unless you're computing the return value somehow.

Oh, and if you feel like doing something switch-like, see here.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
Eugene
  • 755
  • 1
  • 8
  • 13
1

Also use the list for storing the cases, and call corresponding the function by select -

cases = ['zero()', 'one()', 'two()', 'three()']

def zero():
  print "method for 0 called..."
def one():
  print "method for 1 called..."
def two():
  print "method for 2 called..."
def three():
  print "method for 3 called..."

i = int(raw_input("Enter choice between 0-3 "))

if(i<=len(cases)):
  exec(cases[i])
else:
  print "wrong choice"

Also explained at screwdesk.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Harry247
  • 31
  • 1
1

The following works for my situation when I need a simple switch-case to call a bunch of methods and not to just print some text. After playing with lambda and globals it hit me as the simplest option for me so far. Maybe it will help someone also:

def start():
    print("Start")

def stop():
    print("Stop")

def print_help():
    print("Help")

def choose_action(arg):
    return {
        "start": start,
        "stop": stop,
        "help": print_help,
    }.get(arg, print_help)

argument = sys.argv[1].strip()
choose_action(argument)()  # calling a method from the given string
nrp
  • 441
  • 6
  • 16
  • make your action functions closures - i.e. define them in your choose_action function - and you can have access to any arguments etc too :-) i.e. it becomes my solution ... – Tony Suffolk 66 Jun 06 '18 at 16:51
1

And another option:

def fnc_MonthSwitch(int_Month): #### Define a function take in the month variable 
    str_Return ="Not Found"     #### Set Default Value 
    if int_Month==1:       str_Return = "Jan"   
    if int_Month==2:       str_Return = "Feb"   
    if int_Month==3:       str_Return = "Mar"   
    return str_Return;          #### Return the month found  
print ("Month Test 3:  " + fnc_MonthSwitch( 3) )
print ("Month Test 14: " + fnc_MonthSwitch(14) )
M T Head
  • 1,085
  • 9
  • 13
1

Easy to remember:

while True:
    try:
        x = int(input("Enter a numerical input: "))
    except:
        print("Invalid input - please enter a Integer!");
    if x==1:
        print("good");
    elif x==2:
        print("bad");
    elif x==3:
        break
    else:
        print ("terrible");
sudhir tataraju
  • 1,159
  • 1
  • 14
  • 30
0

A switch statement is just syntactic sugar for if/elif/else. What any control statement is doing is delegating the job based on certain condition is being fulfilled - decision path. For wrapping that into a module and being able to call a job based on its unique id, one can use inheritance and the fact that any method in Python is virtual, to provide the derived class specific job implementation, as a specific "case" handler:

#!/usr/bin/python

import sys

class Case(object):
    """
        Base class which specifies the interface for the "case" handler.
        The all required arbitrary arguments inside "execute" method will be
        provided through the derived class
        specific constructor

        @note in Python, all class methods are virtual
    """
    def __init__(self, id):
        self.id = id

    def pair(self):
        """
            Pairs the given id of the "case" with
            the instance on which "execute" will be called
        """
        return (self.id, self)

    def execute(self): # Base class virtual method that needs to be overridden
        pass

class Case1(Case):
    def __init__(self, id, msg):
        self.id = id
        self.msg = msg
    def execute(self): # Override the base class method
        print("<Case1> id={}, message: \"{}\"".format(str(self.id), self.msg))

class Case2(Case):
    def __init__(self, id, n):
        self.id = id
        self.n = n
    def execute(self): # Override the base class method
        print("<Case2> id={}, n={}.".format(str(self.id), str(self.n)))
        print("\n".join(map(str, range(self.n))))


class Switch(object):
    """
        The class which delegates the jobs
        based on the given job id
    """
    def __init__(self, cases):
        self.cases = cases # dictionary: time complexity for the access operation is 1
    def resolve(self, id):

        try:
            cases[id].execute()
        except KeyError as e:
            print("Given id: {} is wrong!".format(str(id)))



if __name__ == '__main__':

    # Cases
    cases=dict([Case1(0, "switch").pair(), Case2(1, 5).pair()])

    switch = Switch(cases)

    # id will be dynamically specified
    switch.resolve(0)
    switch.resolve(1)
    switch.resolve(2)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
damirlj
  • 85
  • 1
  • 2