Reputation: 41
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.
Upvotes: 4
Views: 3241
Reputation: 71074
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.
Upvotes: 4
Reputation: 30977
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.
Upvotes: 9
Reputation: 14373
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')
Upvotes: 1
Reputation: 13257
Ah never mind, this explained it. I was thinking of elif - Switch-case statement in Python
Upvotes: 0
Reputation: 123531
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')
Upvotes: 0
Reputation: 123531
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_)
Upvotes: 3
Reputation: 177895
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)
Upvotes: 2
Reputation: 880977
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=''
...
Upvotes: 9
Reputation: 10618
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])
Upvotes: 1