4

I need a help with some code here. I wanted to implement the switch case pattern in Python, so like some tutorial said, I can use a dictionary for that, but here is my problem:

  # Type can be either create or update or ..
  message = { 'create':msg(some_data),
              'update':msg(other_data)
              # can have more
            }

  return message(type)

but it's not working for me because some_data or other_data can be None (it raises an error if it's none) and the msg function need to be simple (I don't want to put some condition in it).

The problem here is that the function msg() is executed in each time for filling the dict. Unlike the switch case pattern which usually in other programming language don't execute the code in the case unless it's a match.

Is there another way to do this or do I just need to do if elif?

Actually, it's more like this:

message = { 'create': "blabla %s" % msg(some_data),
            'update': "blabla %s" % msg(other_data)
            'delete': "blabla %s" % diff(other_data, some_data)
           }

So lambda don't work here and not the same function is called, so it's more like a real switch case that I need, and maybe I have to think about another pattern.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Unknown
  • 41
  • 1
  • 4
  • 1
    at the very least, square brackets for indexing: `return message[type]` – Andrew Jaffe Oct 08 '10 at 13:21
  • 2
    it's traditional to accept an answer if there's one you like. – Kirk Strauser Oct 08 '10 at 23:02
  • with all my respect to the people that help me here, i didn't like any solution maybe next time if i found another use of switch case i will write a pep and try to get it accepted , like that we will have a "real" switch case statement. – Unknown Oct 10 '10 at 03:56
  • 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:24

9 Answers9

9
message = { 'create':msg(some_data or ''),
            'update':msg(other_data or '')
            # can have more
          }

Better yet, to prevent msg from being executed just to fill the dict:

message = { 'create':(msg,some_data),
            'update':(msg,other_data),
            # can have more
          }
func,data=message[msg_type]
func(data)

and now you are free to define a more sensible msg function which can deal with an argument equal to None:

def msg(data):
    if data is None: data=''
    ...
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • thanks of the response but it's quite confusing , and it's look for me like a no readable code – Unknown Oct 07 '10 at 23:30
  • 5
    @Unknown, if that code looks unreadable, you need to learn to read better. This is the cleanest code you will find for this and nicely uses the fact that functions are first class objects. – aaronasterling Oct 07 '10 at 23:40
  • @AaronMcSmooth, sorry but in a company the level of programmer differ and not all programmer can read such a code and in mine we have a rule that said "do the easiest thing, so that developer after can know what you was doing" so if this look readable for you i will say great but try to read it after 2 mount i'm sure you will ask your self why in the hell i put it like this : with all my respect – Unknown Oct 07 '10 at 23:54
  • 9
    @Unkown. Sorry. I could be kidnapped by organ harvesters and read that code while drugged for an operation X months after writing it. If anybody in your company couldn't read that, then it just makes me pissed that they get to write code for a living and I don't. It's really trivial stuff. – aaronasterling Oct 08 '10 at 00:01
  • @ AaronMcSmooth. :) i understand you, but the point that i want to make is not that the code is not understandable sure it's (for good developer), but why is it like this, someone can come and he will just think that the code can be simplified just by a dict and then booommm, which happen to me old the time :) so for now on i don't put code like this and if i do i just put lot of comment line on the top like why is it like this, basically i think all the time we don't ask our self what the code does but why is it like this don't we ?? :) – Unknown Oct 08 '10 at 00:09
  • 6
    If you're going to write code for the lowest possible denominator, then you're always going to have this issue, even if the code is readable and Pythonic. – Nick Presta Oct 08 '10 at 03:36
  • 5
    @Unknown: at some point, you really have to assume a basic level of knowledge with the programming language in use. If the code uses an esoteric part of the language, you can add a comment, but that code isn't really all that unreadable, and I'm not all that much of a python guy. – atk Oct 08 '10 at 03:37
  • 1
    @Unknown: that dictionaries can be used this way is basic knowledge of Python. The language hasn't got a switch statement because you can either use if / elifs or this construct. I'd say your company needs to invest a bit more in employee training and interviewing if you work in python and can't fathom or remember that functions are first-class objects and dicts are very versatile. – Adriano Varoli Piazza Oct 09 '10 at 18:36
  • @Adriano: allow me to ask: is it you that make the decision of not having a switch case in python ? i bet not, so how can you say that python don't need one, are you just accepting facts or ... ? one more thing who told you that employees in my company aren't good it's not me, your are just interpreting what i wrote with all my respect ,one last thing i was thinking that this was about sharing knowledge and helping but i was mistaken because it's all about people trying to beat other with insult and points i think you should learn to respect other opinion, modesty is a treasure – Unknown Oct 10 '10 at 03:51
  • @Unknown: If I may offer my impression: first, 'switch' is just a shortcut for a series of 'if' statements. A language that supports 'if' does not need to support 'switch' to offer the same functionality. It is the choice of the language designer whether or not to include particular shortcuts, and the Python designers apparently didn't think it was necessary. If you want further details, another question or some research should provide it. – atk Oct 11 '10 at 02:42
  • Re: level of knowledge, you stated, "sorry but in a company the level of programmer differ and not all programmer can read such a code," clearly implying that some of your corporate programmers would not understand the code in the answer. Considering that this is a basic language feature, and it's *un*reasonable to expect programmers using a particular language to *not* know core language features (sorry for the double negative), it is completely reasonable to recommend that your corp's programmers writing python trained so they can read it. If that's not what you meant, then what? – atk Oct 11 '10 at 02:52
  • @Unknown, sorry. It's not "the language hasn't got a switch statement because it has if-elses and this construct", it's "the language hasn't got a switch statement, it has if-elses and this construct". I'm not trying to be insulting, but I understand that you might feel insulted by my statement nonetheless. read @atk's comment on my take on it. – Adriano Varoli Piazza Oct 11 '10 at 17:21
  • @atk : i don't agree and neither all language designer like you said that add the switch case statement in there language , because: let's begin with if else statement what it's does it's that it test "each time" if the condition is correct to make the decision to execute the code or not , in the other hand the switch case statement get a var and by knowing it value it's know where to go(under-hood you can put it like the old goto) ; see this http://en.wikipedia.org/wiki/Switch_statement – Unknown Oct 12 '10 at 12:10
  • @Unknown: You don't agree that language designers have to choose whether or not to include the switch statement? Or you don't agree that the same functionality can be achieved with if/else? Or are you arguing that switch is essentially a form of goto, and if/else is not (or similarly that one is implemented if a jmp instruction and the other is not)? – atk Oct 12 '10 at 12:55
  • @atk : this "'switch' is just a shortcut for a series of 'if' statements" i don't agree about, this "A language that supports 'if' does not need to support 'switch' to offer the same functionality" i don't agree about, "It is the choice of the language designer whether or not to include particular shortcuts" this i agree except the "particular shortcuts" . about "you don't agree that the same functionality can be achieved with if/else" sure we can but i'm talking about optimization here especially with lot of condition to test, and about readability (if elif elif ...) – Unknown Oct 12 '10 at 13:58
  • @atk : the wikipedia link explain the difference between a switch case and if else i think, if it's not clear here is one more : http://www.eventhelix.com/RealtimeMantra/Basics/CToAssemblyTranslation3.htm have fun :) – Unknown Oct 12 '10 at 14:04
  • @Unknown: What functionality is available with switch that isn't available with if/else? If the language designer doesn't decide what to put in the language, who does? What optimization can the compiler perform with switch that can't be performed with if/else? I'll agree that switch can, in certain cases, be more readable than if/else, but this is the first time you brought up readability in the discussion of switch vs if/else. Let's discuss the stuff that doesn't make sense :) – atk Oct 12 '10 at 15:17
  • @atk: ok let's begging with "If the language designer doesn't decide what to put in the language, who does?" i don't what you mean here by designer but python has something called pep and BDFL check this python.org/dev/peps/pep-0001 , and for the other question i just found this : http://www.python.org/dev/peps/pep-0275/ but it's was rejected two time heheheehe :) – Unknown Oct 12 '10 at 18:04
  • @Unknown: the language designer would be the individual or group that decides what fets into the language and what doesn't. Just like every computer project. If the language designer doesnt decide what gets into the language then who does? – atk Oct 12 '10 at 22:28
9

It sounds like you're complicating this more than you need to. If you want it simple, use:

if mytype == 'create':
    return msg(some_data)
elif mytype == 'update':
    return msg(other_data)
else:
    return msg(default_data)

You don't have to use dicts and function references just because you can. Sometimes a boring, explicit if/else block is exactly what you need. It's clear to even the newest programmers on your team and won't call msg() unnecessarily, ever. I'm also willing to bet that this will be faster than the other solution you were working on unless the number of cases grows large and msg() is lightning fast.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kirk Strauser
  • 30,189
  • 5
  • 49
  • 65
  • 1
    @jathanism : sorry but i don't agree; try to do if/else with more condition it's definitely slower, and it's in condition like this where the switch/case come in hand , i don't want to start a complain but this is what i hate in python the fact that some functionality are missing ((real)thread, switch/case ..) and when you want to use them you have to do some hack to replace them (processes , dictionary ...) and it's never the same , with all my respect – Unknown Oct 08 '10 at 10:32
  • 3
    @Unknown: you can't know that unless you've benchmarked it. In an idealized situation where you can set up your dictionary once and then reuse it many times, and there are enough potential branches to incur noticeable overhead, the dict approach may be faster. If those aren't all true, and you can optimize your if/elif block so that the most common condition is tested first, it may actually be slower. – Kirk Strauser Oct 08 '10 at 16:29
  • Some Guy: what i actually said was that if else is slower than a switch case in language that implement it "in many cases" – Unknown Oct 10 '10 at 03:24
4

Turns out the jokes on me and I was pwned in the switch inventing game five years before I even learned Python: Readable switch construction without lambdas or dictionaries. Oh well. Read below for another way to do it.


Here. Have a switch statement (with some nice cleanups by @martineau):

with switch(foo):

    @case(1)
    def _():
        print "1"

    @case(2)
    def _():
        print "2"

    @case(3)
    def _():
        print "3"

    @case(5)
    @case(6)
    def _():
        print '5 and 6'

    @case.default
    def _():
        print 'default'

I'll toss in the (moderately) hacked stack, abused decorators and questionable context manager for free. It's ugly, but functional (and not in the good way). Essentially, all it does is wrap the dictionary logic up in an ugly wrapper.

import inspect

class switch(object):
    def __init__(self, var):
        self.cases = {}
        self.var = var


    def __enter__(self):
        def case(value):
            def decorator(f):
                if value not in self.cases:
                    self.cases[value] = f
                return f
            return decorator

        def default(f):
            self.default = f
            return f
        case.default = default

        f_locals = inspect.currentframe().f_back.f_locals
        self.f_locals = f_locals.copy()
        f_locals['case'] = case

    def __exit__(self, *args, **kwargs):
        new_locals = inspect.currentframe().f_back.f_locals
        new_items = [key for key in new_locals if key not in self.f_locals]
        for key in new_items:
             del new_locals[key]              # clean up
        new_locals.update(self.f_locals)      # this reverts all variables to their
        try:                                  # previous values
            self.cases[self.var]()
        except KeyError:
            try:
                getattr(self, 'default')()
            except AttributeError:
                pass

Note that the hacked stack isn't actually necessary. We just use it to create a scope so that definitions that occur in the switch statement don't leak out into the enclosing scope and to get case into the namespace. If you don't mind leaks (loops leak for instance), then you can remove the stack hack and return the case decorator from __enter__, using the as clause on the with statement to receive it in the enclosing scope.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • 2
    Dear God. I gave you +1 for commendable insanity. – Kirk Strauser Oct 08 '10 at 16:33
  • +1 for creative abuse of `with` and decorators -- but I doubt the OP would like it give he/she thought `func,data=message[msg_type]` was overly complicated. – martineau Oct 09 '10 at 15:35
  • 1
    FWIW, if you moved the `self.f_locals = copy.copy(f_locals)` up one line in `__enter()`, you wouldn't need the `del new_locals['case']` in `__exit__()`. – martineau Oct 09 '10 at 16:05
  • 1
    Easily fixed bug. The nested `decorator(f)` function needs a `return f` at the end so the chaining of `@case(X)` decorators works -- otherwise setting `foo = 5` results in a `TypeError: 'NoneType' object is not callable` error in the example. Nonetheless, a very clever solution. – martineau Oct 09 '10 at 18:16
  • @martineau, thanks for the fixes. The last bug was just a copying error resulting from editing two versions at once. Embarrassed I missed the first one though. Oh well, I probably should have been sleeping when I was writing this so I'll let that explain it. The point is not that OP like it but that OP see a complicated solution to the problem to put the simple ones in perspective. – aaronasterling Oct 09 '10 at 19:12
  • No big deal. I found the first only because I was closely following each step in both halves of the "stack hack" you deprecate so much. What really intrigued me was how it was able to inject 'case' into the caller's local namespace -- and then later remove it -- via the context handler protocol. Seemed like something worth understanding. – martineau Oct 10 '10 at 07:01
  • Another small simplification to consider: If the `self.f_locals = copy.copy(f_locals)` was changed to `self.f_locals = f_locals.copy()`, the `import copy` near the top could go away, too. – martineau Oct 10 '10 at 07:32
  • @martineau. Good eye. I always forget about the copy method on dicts. I should probably just start pretending that that module is deprecated and start figuring out a way to do everything without it. – aaronasterling Oct 10 '10 at 07:39
  • @martineau. I fixed it up and also fixed two buts that I saw. 1) If 'default' failed to be called and a case didn't match, it would blowup with an attribute error. 2) if `case` was a variable outside of the switch 'statement', then it would get disappeared. both fixed. Thanks for your suggestions and getting me to look at it again. – aaronasterling Oct 10 '10 at 07:49
  • But wait, there's more! You could create a default `default()` by adding a `self.default = lambda: None` to `__init__()`. This would allow you to remove the `try/except` you just added around the `getattr(self, 'default')()` call. Regardless, the latter can be changed into a simpler `self.default()`. Maybe @Unknown had a point about too much complexity... ;-) – martineau Oct 10 '10 at 09:31
  • @martineau, it's no good changing it to `self.default()` because then python will treat it as a bound method call and pass it an instance. This would require the user to write the default method to accept `self` which sort of ruins it. I could store it in a list and pull it out that way but might as well just use `getattr`. – aaronasterling Oct 10 '10 at 10:05
  • Dunno, I understand what you say, but it seems to work perfectly for me with the changes just as I described, both with *and* without a `@case.default` function being there. Actually I may have found a serious (unrelated) limitation -- that it can't be used in function bodies unless the class is defined there -- that I'm looking into... – martineau Oct 10 '10 at 10:36
  • @matineau, you're right about the `lambda` I had a misunderstanding about that. I'm going to leave it as is so as not to bump it for the sake of small change like that though – aaronasterling Oct 10 '10 at 10:53
  • @martineau, you're also right about the limitation. backing out two frames fixes it but then breaks it for global code. For a recursive function, you need three `f_backs`. I suspect that I'm really just using `globals` in which case, we could remove `inspect` as well. – aaronasterling Oct 10 '10 at 11:01
  • I suspect getting rid of `inspect` would be a good thing, especially if along the way it can be made to work in more or all locations. The `lambda`/`self.default()` modification is nothing comparatively. – martineau Oct 10 '10 at 11:42
3

Here's something a little different (although somewhat similar to @Tumbleweed's) and arguably more "object-oriented". Instead of explicitly using a dictionary to handle the various cases, you could use a Python class (which contains a dictionary).

This approach provides a fairly natural looking translation of C/C++ switch statements into Python code. Like the latter it defers execution of the code that handles each case and allows a default one to be provided.

The code for each switch class method that corresponds to a case can consist of more than one line of code instead of the single return <expression> ones shown here and are all compiled only once. One difference or limitation though, is that the handling in one method won't and can't be made to automatically "fall-though" into the code of the following one (which isn't an issue in the example, but would be nice).

class switch:
    def create(self): return "blabla %s" % msg(some_data)
    def update(self): return "blabla %s" % msg(other_data)
    def delete(self): return "blabla %s" % diff(other_data, some_data)
    def _default(self): return "unknown type_"
    def __call__(self, type_): return getattr(self, type_, self._default)()

switch = switch() # only needed once

return switch(type_)
martineau
  • 119,623
  • 25
  • 170
  • 301
2

You could hide the evaluation inside a lambda:

message = { 'create': lambda: msg(some_data),
            'update': lambda: msg(other_data),
          }
return message[type]()

As long as the names are all defined (so you don’t get a NameError), you could also structure it like this:

message = { 'create': (msg, some_data),
            'update': (other_func, other_data),
          }
func, arg = message[type]
return func(arg)
Josh Lee
  • 171,072
  • 38
  • 269
  • 275
1

You can delay execution of the match using lambda:

return {
    'create': lambda: msg(some_data),
    'update': lambda: msg(other_data),
    # ...
}[type]()

If all the cases are just calling msg with different arguments, you can simplify this to:

return msg({
    'create': some_data,
    'update': other_data,
    # ...
}[type])
Pi Delport
  • 10,356
  • 3
  • 36
  • 50
1

Create a new class and wrap the data/arguments in an object - so instead of binding data by passing arguments - you could just let a function decide which parameters it needs...

class MyObj(object):
    def __init__(self, data, other_data):
        self.data = data
        self.other_data = other_data

    def switch(self, method_type):
        return {
                   "create": self.msg,
                   "update": self.msg,
                   "delete": self.delete_func,
                }[method_type]()

    def msg(self):
        # Process self.data
        return "Hello, World!!"

    def delete_func(self):
        # Process other data self.other_data or anything else....
        return True

if "__main__" == __name__:
    m1 = MyObj(1,2)
    print m1.switch('create')
    print m1.switch('delete')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
shahjapan
  • 13,637
  • 22
  • 74
  • 104
0

Ah never mind, this explained it. I was thinking of elif - Switch-case statement in Python

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
griotspeak
  • 13,022
  • 13
  • 43
  • 54
  • 1
    sorry, if i didn't mention that but it's python , and python don't have one – Unknown Oct 07 '10 at 23:28
  • Yeah, I remembered after I hit submit and looked at my old code. Haven't used python in a while and I was only fundamental really – griotspeak Oct 07 '10 at 23:31
  • A switch statement was introduced [with Python 3.10 (2021)](https://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python/60211#60211). – Peter Mortensen Oct 05 '21 at 10:46
0

Since the code you want to execute in each case is from a safe source, you could store each snippet in a separate string expression in a dictionary and do something along these lines:

message = { 'create': '"blabla %s" % msg(some_data)',
            'update': '"blabla %s" % msg(other_data)',
            'delete': '"blabla %s" % diff(other_data, some_data)'
          }

return eval(message[type_])

The expression on the last line could also be eval(message.get(type_, '"unknown type_"')) to provide a default. Either way, to keep things readable the ugly details could be hidden:

switch = lambda type_: eval(message.get(type_, '"unknown type_"'))

return switch(type_)

The code snippets can even be precompiled for a little more speed:

from compiler import compile # deprecated since version 2.6: Removed in Python 3

for k in message:
    message[k] = compile(message[k], 'message case', 'eval')
martineau
  • 119,623
  • 25
  • 170
  • 301