Switch in Python [duplicate]
Asked Answered
P

8

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?

Picaroon answered 17/1, 2012 at 13:59 Comment(9)
What on earth is this meant to do?!?! (It's probably failing because list.append returns None and modifies the original list in place.)Roughhouse
Please tell me you're kidding when you call this "more pretty"...Vaginal
Readable code is pretty code. Don't worry about this, use if statements...Phytosociology
I really advise against what you're trying to do! A smart use of dictionary will do that for you in a pythonic way!Severity
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.Godevil
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.Patriotism
Also, @Picaroon - 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.Godevil
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.Gaming
The canonical is Replacements for switch statement in Python? [sic]. It also has the switch statement introduced with Python 3.10 (2021).Commodore
G
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
Godevil answered 17/1, 2012 at 14:5 Comment(6)
for (c), i think this is what the algo wanted to do... but failed to do it.Tigon
This. Python doesn't have a switch statement by design.Codger
@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.Godevil
@Marcin: it was rejected as PEP 3103 by the BDFL because nobody wants it.Roughhouse
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.Picaroon
@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.Godevil
L
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)
Leverhulme answered 17/1, 2012 at 15:1 Comment(0)
H
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")
Himself answered 25/2, 2022 at 4:48 Comment(0)
S
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.

Soembawa answered 17/1, 2012 at 14:6 Comment(0)
T
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
Tigon answered 17/1, 2012 at 14:6 Comment(1)
The dict-switch is a legit technique, but it's used for something else.Soembawa
L
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
Liquidate answered 17/1, 2012 at 15:27 Comment(0)
H
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.

Hyaline answered 17/1, 2012 at 15:35 Comment(0)
M
-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.

Manque answered 2/7, 2017 at 18:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.