13

I have tried making a switch like statement in python, instead of having a lot of if statements.

The code looks like this:

def findStuff(cds):
    L=[]
    c=0
    for i in range(0, len(cds), 3):
        a=differencesTo(cds[i:i+3])
        result = {
            a[2][0]==1: c=i+1,
            a[2][1]==1: c=i+2,
            a[2][2]==1: c=i+3,
            a[1]==1: L.append((cds[i:i+3], a[0], c))
        } 
    return L

My problem is, that this does not work. (Works with if statements, but this would in my opinion be more pretty).

I have found some examples of switches in Python, and they follow this structure. Can anyone help me?

X-Pender
  • 213
  • 1
  • 2
  • 7
  • 7
    What on earth is this meant to do?!?! (It's probably failing because `list.append` returns `None` and modifies the original list in place.) – Katriel Jan 17 '12 at 14:02
  • 8
    Please tell me you're kidding when you call this "more pretty"... – NPE Jan 17 '12 at 14:08
  • Readable code is pretty code. Don't worry about this, use if statements... – ingh.am Jan 17 '12 at 14:14
  • 2
    I really advise against what you're trying to do! A smart use of `dictionary` will do that for you in a pythonic way! – Rik Poggi Jan 17 '12 at 14:27
  • 2
    Can I suggest that people don't downvote this question: It is a useful *question*, and when this comes up again, other questions can be closed as duplicates. – Marcin Jan 17 '12 at 14:49
  • A much better question would be to post *working* `if..elif..else` code and ask how it can be written more clearly. I can't even tell from your code what you are trying to achieve. – Duncan Jan 17 '12 at 14:49
  • Also, @X-Pender - you should edit this to explain what you think this code is supposed to do. I would be surprised if it even parses as valid. – Marcin Jan 17 '12 at 15:17
  • Writing code generally writing code that others can understand. Ignoring standard Python idioms because you think Python is broken means you are necessarily writing code that no one but you will understand. Please stop. Furthermore, ``findStuff`` is a terrible name for a function. – nfirvine Jan 17 '12 at 22:48
  • The canonical is *[Replacements for switch statement in Python?](https://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python/60211#60211)* [sic]. It also has the switch statement introduced with Python 3.10 (2021). – Peter Mortensen Oct 05 '21 at 11:21

8 Answers8

21

(a) I fail to see what is wrong with if...elif...else

(b) I assume that python does not have a switch statement for the same reason that Smalltalk doesn't: it's almost completely redundant, and in the case where you want to switch on types, you can add an appropriate method to your classes; and likewise switching on values should be largely redundant.

Note: I am informed in the comments that whatever Guido's reason for not creating a switch in the first place, PEPs to have it added were rejected on the basis that support for adding such a statement is extremely limited. See: http://www.python.org/dev/peps/pep-3103/

(c) If you really need switching behaviour, use a hashtable (dict) to store callables. The structure is:

switch_dict = {
    Foo: self.doFoo,
    Bar: self.doBar,
    }

func = switch_dict[switch_var]
result = func() # or if they take args, pass args
Marcin
  • 48,559
  • 18
  • 128
  • 201
  • for (c), i think this is what the algo wanted to do... but failed to do it. – tito Jan 17 '12 at 14:07
  • 8
    This. Python doesn't have a switch statement *by design*. – Doches Jan 17 '12 at 14:07
  • @Doches: Is there anywhere that documents the reason why that choice was made? I'd be interested to know if it is something other than my guess. – Marcin Jan 17 '12 at 14:09
  • 2
    @Marcin: it was rejected as [PEP 3103](http://www.python.org/dev/peps/pep-3103/) by the BDFL because nobody wants it. – Katriel Jan 17 '12 at 14:11
  • Sure I can just make if statements, this was just not the way I would do it in Java, so I thought a switch like idea would be better. – X-Pender Jan 18 '12 at 09:17
  • 1
    @X-Pender: Copying java practices is never a good idea. In any case, if you want to use a switch construct, I have explained how. – Marcin Jan 18 '12 at 09:22
7

There's nothing wrong with a long if:

if switch == 'case0':
   do_case0()
elif switch == 'case1':
   do_case1()
elif switch == 'case2':
   do_case2()
...

If that's too long winded, or if you have a lot of cases, put them in a dictionary:

switch = {'case0': do_case0, 'case1': do_case1, 'case2': do_case2, ...}
switch[case_variable]()
// Alternative:
(switch[case_variable]).__call__()

If your conditions are a bit more complex, you need to think a little about your data structures. e.g.:

switch = {
    (0,21): 'never have a pension',
    (21,50): 'might have a pension',
    (50,65): 'definitely have a pension',
    (65, 200): 'already collecting pension'
}
for key, value in switch.items():
    if key[0] <= case_var < key[1]:
        print(value)
wordbug
  • 641
  • 5
  • 10
snim2
  • 4,004
  • 27
  • 44
3

Other ans are suitable for older version of python. For python v3.10+ you can use match/case which is more powerful than general switch/case construct.

def something(val):
    match val:
        case "A":
            return "A"
        case "B":
            return "B"
        case "C":
            return "C"
        case _:
            return "Default"

something("A")
k33da_the_bug
  • 812
  • 8
  • 16
1

Assignment in Python is a statement, and cannot be a part of expression. Also, using literal in this way evaluates everything at once, which is probably not what you want. Just use ifs, you won't gain any readability by using this.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
0

I don't know which article you've found to do something like this, but this is really messy: the whole result diction will be always evaluated, and instead of doing only part of the work (as a switch / if do), you'll do the whole work everytime. (even if you use only a part of the result).

Really, a fast switch statement in Python is using "if":

if case == 1:
  pass
elif case == 2:
  pass
elif case == 3:
  pass
else:
  # default case
  pass
tito
  • 12,990
  • 1
  • 55
  • 75
0

With "get" method, you can have the same effect as "switch..case" in C.

Marcin example :

switch_dict = {
    Foo: self.doFoo,
    Bar: self.doBar,
}

func = switch_dict.get(switch_var, self.dodefault)
result = func() # or if they take args, pass args
no_name
  • 295
  • 1
  • 2
  • 14
0

You can do something like what you want, but you shouldn't. That said, here's how; you can see how it does not improve things.

The biggest problem with the way you have it is that Python will evaluate your tests and results once, at the time you declare the dictionary. What you'd have to do instead is make all conditions and the resulting statements functions; this way, evaluation is deferred until you call them. Fortunately there is a way to do this inline for simple functions using the lambda keyword. Secondly, the assignment statement can't be used as a value in Python, so our action functions (which are executed if the corresponding condition function returns a truthy value) have to return a value that will be used to increment c; they can't assign to c themselves.

Also, the items in a dictionary aren't ordered, so your tests won't necessarily be performed in the order you define them, meaning you probably should use something other than a dictionary that preserves order, such as a tuple or a list. I am assuming you want only ever one case to execute.

So, here we go:

def findStuff(cds):

    cases = [ (lambda: a[2][0] == 1, lambda: i + 1),
              (lambda: a[2][1] == 1, lambda: i + 2),
              (lambda: a[2][2] == 1, lambda: i + 3),
              (lambda: a[1] == 1,    lambda: L.append(cds[i:i+3], a[0], c) or 0)
            ]

    L=[]
    c=0
    for i in range(0, len(cds), 3):
        a=differencesTo(cds[i:i+3])
        for condition, action in cases:
            if condition():
                c += action()
                break
    return L

Is this more readable than a sequence of if/elif statements? Nooooooooooooo. In particular, the fourth case is far less comprehensible than it should be because we are having to rely on a function that returns the increment for c to modify a completely different variable, and then we have to figure out how to get it to return a 0 so that c won't actually be modified. Uuuuuugly.

Don't do this. In fact this code probably won't even run as-is, as I deemed it too ugly to test.

kindall
  • 178,883
  • 35
  • 278
  • 309
-1

While there is nothing wrong with if..else, I find "switch in Python" still an intriguing problem statement. On that, I think Marcin's (deprecated) option (c) and/or Snim2's second variant can be written in a more readable way.

For this we can declare a switch class, and exploit the __init__() to declare the case we want to switch, while __call__() helps to hand over a dict listing the (case, function) pairs:

class switch(object):
    def __init__(self, case):
        self._case = case

    def __call__(self, dict_):
        try:
            return dict_[self._case]()
        except KeyError:
            if 'else' in dict_:
                return dict_['else']()
            raise Exception('Given case wasn\'t found.')

Or, respectively, since a class with only two methods, of which one is __init__(), isn't really a class:

def switch(case):
    def cases(dict_):
        try:
            return dict_[case]()
        except KeyError:
            if 'else' in dict_:
                return dict_['else']()
            raise Exception('Given case wasn\'t found.')
    return cases

(note: choose something smarter than Exception)

With for example

def case_a():
    print('hello world')

def case_b():
    print('sth other than hello')

def default():
    print('last resort')

you can call

switch('c') ({
    'a': case_a,
    'b': case_b,
    'else': default
})

which, for this particular example would print

last resort

This doesn't behave like a C switch in that there is no break for the different cases, because each case executes only the function declared for the particular case (i.e. break is implicitly always called). Secondly, each case can list exactly only one function that will be executed upon a found case.