6

Recently, I saw some discussions online about how there is no good "switch / case" equivalent in Python. I realize that there are several ways to do something similar - some with lambda, some with dictionaries. There have been other StackOverflow discussions about the alternatives. There were even two PEPs (PEP 0275 and PEP 3103) discussing (and rejecting) the integration of switch / case into the language.

I came up with what I think is an elegant way to do switch / case.

It ends up looking like this:

from switch_case import switch, case         # note the import style

x = 42
switch(x)                                    # note the switch statement
if case(1):                                  # note the case statement
    print(1)
if case(2):
    print(2)
if case():                                   # note the case with no args
    print("Some number besides 1 or 2")

So, my questions are: Is this a worthwhile creation? Do you have any suggestions for making it better?

I put the include file on github, along with extensive examples. (I think the entire include file is about 50 executable lines, but I have 1500 lines of examples and documentation.) Did I over-engineer this thing, and waste a bunch of time, or will someone find this worthwhile?

Edit:

Trying to explain why this is different from other approaches:
1) Multiple paths are possible (executing two or more cases), which is harder in the dictionary method.
2) can do checking for comparisons other than "equals" (such as case(less_than(1000)).
3) More readable than the dictionary method, and possibly if/elif method
4) can track how many True cases there were.
5) can limit how many True cases are permitted. (i.e. execute the first 2 True cases of...)
6) allows for a default case.

Here's a more elaborate example:

from switch_case import switch, case, between

x=12
switch(x, limit=1)                # only execute the FIRST True case
if case(between(10,100)):         # note the "between" case Function
    print ("%d has two digits."%x)
if case(*range(0,100,2)):         # note that this is an if, not an elif!
    print ("%d is even."%x)       # doesn't get executed for 2 digit numbers,
                                  # because limit is 1; previous case was True.
if case():
    print ("Nothing interesting to say about %d"%x)



# Running this program produces this output:

12 has two digits.

Here's an example attempting to show how switch_case can be more clear and concise than conventional if/else:

# conventional if/elif/else:
if (status_code == 2 or status_code == 4 or (11 <= status_code < 20) 
          or status_code==32):
    [block of code]
elif status_code == 25 or status_code == 45:
    [block of code]
if status_code <= 100:
    [block can get executed in addition to above blocks]

# switch_case alternative (assumes import already)
switch(status_code)
if case (2, 4, between(11,20), 32):   # significantly shorter!
    [block of code]
elif case(25, 45):
    [block of code]
if case(le(100)):
    [block can get executed in addition to above blocks]

The big savings is in long if statements where the same switch is repeated over and over. Not sure how frequent of a use-case that is, but there seems to be certain cases where this makes sense.

The example file on github has even more examples.

jerfelix
  • 79
  • 1
  • 5
  • 4
    This should be submitted as an another PEP. – Tadeusz A. Kadłubowski Mar 26 '11 at 07:39
  • How is this different from a conventional `if`? – MAK Mar 26 '11 at 08:13
  • 2
    The approach is interesting, but with the edit, this question is turning into a flat-out advertisement. The main weakness of your approach is that the `switch` statement is not lexically bound to your case statements, which would cause some pretty bizarre behavior if, for whatever reason, a `switch` is disconnected from its `case` blocks. The obvious solution is to make `switch` into a context manager, so that the user can do `with switch(x):\n...if case(val):\n......`. – phooji Mar 26 '11 at 08:17
  • The standard select/case differs is a special case of the conventional if. It's the case where the same value is compared multiple times against various cases. One argued benefit of select/case is that you don't need to repeat that same "if x=="... stuff over and over. Example: if case(1, contained_in(range(10,20)),21,31,41) ... in conventional if, this would read "if x==1 or x in range(10,20) or x==21 or x==31, or x==41". Case is more concise in this, um, case. – jerfelix Mar 26 '11 at 08:22
  • > Can you do nested switches? >> yes, I cover that in the examples, lines 578 through 697 in https://github.com/jerfelix/switch_case/blob/master/switch_case_examples.py – jerfelix Mar 26 '11 at 08:23
  • Your examples demonstrates perfectly well why switch/case is not good idea to have in python. All your examples can be written to be more readable with existing constructs. Thanks – eat Mar 26 '11 at 08:56
  • @phooji Thanks for the feedback. Sorry, not trying to advertise, simply trying to see if there's value here, and looking to make it better. I considered your other issue, and agree, and my examples show how I overcame that. I tried the "with" method, and didn't like the multiple indents (as mentioned in one of the rejected PEPs). Instead, I went with binding switch to named case variables, as shown in my nested example: starting on line 578 of https://github.com/jerfelix/switch_case/blob/master/switch_case_examples.py I do appreciate the feedback! – jerfelix Mar 26 '11 at 10:43
  • possible duplicate of [Replacements for switch statement in python?](http://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python) – Anderson Green Aug 11 '13 at 16:47

6 Answers6

6

So, my questions are: Is this a worthwhile creation?

No.

Do you have any suggestions for making it better?

Yes. Don't bother. What has it saved? Seriously? You have actually made the code more obscure by removing the variable x from each elif condition.. Also, by replacing the obvious elif with if you have created intentional confusion for all Python programmers who will now think that the cases are independent.

This creates confusion.

The big savings is in long if statements where the same switch is repeated over and over. Not sure how frequent of a use-case that is, but there seems to be certain cases where this makes sense.

No. It's very rare, very contrived and very hard to read. Seeing the actual variable(s) involved is essential. Eliding the variable name makes things intentionally confusing. Now I have to go find the owning switch() function to interpret the case.

When there are two or more variables, this completely collapses.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • The cases are independent, if it's "if" and not "elif". If you want to use elif, you can do that, too. Obviously my examples are contrived for demonstration purposes, but I'm sure there is value in select/case in other languages (which also have if/then/else constructs). So it just feels like there are cases where select/case would help. I can think of software I have written in other languages where select/case was absolutely a life saver. Appreciate the feedback though. It was a fun exercise wring this, just not sure if someone can put it to good use. – jerfelix Mar 26 '11 at 13:11
  • "The cases are independent" totally violates my understanding of "switch". See how this simply adds confusion? "I can think of software I have written in other languages where select/case was absolutely a life saver." Odd, the only examples I can find are about the same as "if-elif". I can't see how this switch clarifies anything. – S.Lott Mar 26 '11 at 18:15
  • I understand your feedback and appreciate it. My comment that cases are independent- I'm trying to say "it's like C" where it can fall through. And you can use elif if you want to prevent the fall through behavior. Your choice. And the software that had numerous cases with repeated comparisons over and over was for tax calculations and financial applications in the 80s. Maybe not as common now. I won't count on you as a happy user. :) – jerfelix Mar 26 '11 at 23:55
  • I'm trying to say that C's ability to fall through is a terrible source of bugs and confusion. The **Standard Expectation** is that cases are independent. That's the usual expectation from a switch in all languages except C. C is not a lofty goal which we all seek to achieve. C contains some hacks we seek to avoid. Modeling the C switch creates confusion. My answer reflects that confusion. "numerous cases with repeated comparisons" is something we use functions and classes for. We don't need this confusing switch. – S.Lott Mar 27 '11 at 01:45
5

There have been a plethora of discussions that address this issue on Stackoverflow. You can use the search function at the top to look for some other discussions.

However, I fail to see how your solution is better than a basic dictionary:

def switch(x):
    return {
        1 : 1,
        2 : 2,
    }[x]

Although, adding a default clause is non-trivial with this method. However, your example seems to replicate a complex if/else statement anyway ? Not sure if I would include an external library for this.

Russell Dias
  • 70,980
  • 5
  • 54
  • 71
  • Other benefits to my new switch_case.py library: 1) Multiple paths are possible (executing two or more cases). 2) can do checking for comparisons other than "equals" (such as case(less_than(1000)), 3) More readable than the dictionary method that you describe. 4) can track how many True cases there were. 5) can limit how many cases are permitted. 6) allows for a default case. – jerfelix Mar 26 '11 at 07:52
  • 3
    Don't forget the get() method. return { 1: 1, 2:2 }.get(x, 'default-value') – Steve Howard Mar 26 '11 at 08:21
  • @jerflix: I apologize for not reading into your implementation. I'll go over it later tonight. It does seem interesting. – Russell Dias Mar 26 '11 at 08:45
4

IMHO, the main reason for the switch statement to exist is so it can be translated/compiled into a (very fast) jump table. How would your proposed implementation accomplish that goal? Python's dictionaries do it today, as other posters have shown.

Secondarily, I guess a switch statement might read more clearly than the alternatives in some languages, but in python's case I think if/elif/else wins on clarity.

ʇsәɹoɈ
  • 22,757
  • 7
  • 55
  • 61
  • So far, no comments here have shown a Python dictionary of a jump table; they have shown dictionary look-ups of ints, but not of callables. I agree, if it's speed that you are after, then a jump table is ideal, but a dictionary jump table in Python is not very clear coding for the novice. (As I understand it, the values of the dict need to be functions, and then you look up the key and call the value, like d[key](args) which gets ugly, IMHO.) I was trying to create something that was readable for the novice, and where you didn't have to repeat your switch variable many times. – jerfelix Mar 26 '11 at 10:20
  • @jerfelix, well, if you do something like `d = {1: myfunc(args), }` you'll get the value of myfunc everytime you look it up, using the args supplied. This isn't hard for anyone with a bit of programming exp to understand. `d[k](args)` isn't so bad if your dict and key are named appropriately. ex `NEW, UPDATE, CLEAR = range(3); myobjtbl = {NEW: myobj.new, UPDATE: myobj.update, CLEAR: myobj.clear}; myobjtbl[NEW](args);` this isn't so bad. Being short and lacking descriptive names is not a good thing for long term maintainability either, regardless of language. Nor do they needToBeReallyLong). – Mike Ramirez Mar 26 '11 at 11:14
  • @GufyMike Hadn't thought of that, but I'm not understanding how that would work. I was thinking of d={1:myfunc,} and then calling d[1](x) (to switch on x). I tried your idea and can't seem to get it. See: http://pastebin.com/Z4M7urPU Can you clarify? – jerfelix Mar 26 '11 at 11:26
  • @GufyMike Sorry, I think I commented before you edited, so my previous comment might not make sense. I think I understand what you are saying, but I think your syntax `d = {1: myfunc(args), }` shouldn't have `(arg)` in there. Yes, you are right in that there are ways to make code hard to maintain with either short names or long names. I was hoping that "switch_case" would provide one more tool for readability. Plus it was fun to write. :-) Thanks for the critical eye. http://docs.python.org/faq/design.html#why-isn-t-there-a-switch-or-case-statement-in-python has examples like yours. – jerfelix Mar 26 '11 at 11:47
  • @jerfleix I might have misrepresented myself, the first example was meant to illustrate how that wouldn't work right. Your result is the expected one. The second example is what I typically do. With the right naming, looks improve. and yeah, agreed just wrote a singal dispatcher.... plenty around but sometimes you gotta. – Mike Ramirez Mar 26 '11 at 11:47
3

I have always just used dictionaries, if/elses, or lambdas for my switch like statements. Reading through your code tho =)

docs:

why-isn-t-there-a-switch-or-case-statement-in-python

dting
  • 38,604
  • 10
  • 95
  • 114
3
from pyswitch import Switch   # pyswitch can be found on PyPI

myswitch = Switch()

@myswitch.case(42)
def case42(value):
    print "I got 42!"

@myswitch.case(range(10))
def caseRange10(value):
    print "I got a number from 0-9, and it was %d!" % value

@myswitch.caseIn('lo')
def caseLo(value):
    print "I got a string with 'lo' in it; it was '%s'" % value

@myswitch.caseRegEx(r'\b([Pp]y\w)\b')
def caseReExPy(matchOb):
    print r"I got a string that matched the regex '\b[Pp]y\w\b', and the match was '%s'" % matchOb.group(1)

@myswitch.default
def caseDefault(value):
    print "Hey, default handler here, with a value of %r." % value

myswitch(5)  # prints: I got a number from 0-9, and it was 5!
myswitch('foobar')  # prints: Hey, default handler here, with a value of foobar.
myswitch('The word is Python')  # prints: I got a string that matched the regex '\b[Pp]y\w\b', and the match was 'Python'

You get the idea. Why? Yep, dispatch tables are the way to go in Python. I just got tired of writing them over and over, so I wrote a class and some decorators to handle it for me.

Michael Kent
  • 1,736
  • 12
  • 11
1

Update 2021: match-case introduced in Python 3.10

This hotly debated topic can now be closed.

In fact Python 3.10 released in October 2021 introduces structural pattern matching which brings a match-case construct to the language.

See this related answer for details.

divenex
  • 15,176
  • 9
  • 55
  • 55