views:

10679

answers:

15

I want to write a function in python that returns different fixed values based on the value of an input index. In other languages I would use a switch or case statement, but python does not appear to have a switch statement. What are the recommended python solutions in this scenario?

+49  A: 

You could use a dictionary:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
Greg Hewgill
What happens if x is not found?
Nick
@nick: you can use defaultdict
Eli Bendersky
This is not a true switch/case... please see my response below
daniel
I'd recommend putting the dict outside of the function if performance is an issue, so it doesn't re-build the dict on every function call
Claudiu
+1 it's usefull
DrFalk3n
+24  A: 

I've always liked doing it this way

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

From here

Mark Biek
great method, combined with get() to handle default is my best choice too
AlberT
He's asking for fixed values. Why generate a function to calculate something when it's a lookup? Interesting solution for other problems though.
Nick
+1  A: 

If you are really just returning a predetermined, fixed value, you could create a dictionary with all possible input indexes as the keys, along with their corresponding values. Also, you might not really want a function to do this - unless you're computing the return value somehow.

Oh, and if you feel like doing something switch-like, see here.

Eugene
+9  A: 

In addition to the dictionary methods (which I really like, BTW), you can also use if-elif-else to obtain the switch/case/default functionality:

if x=='a':
    # Do the thing
elif x=='b':
    # Do the other thing
else:
    # Do the default

This of course is not identical to switch/case - you cannot have fall-through as easily as leaving off the break; statement, but you can have a more complicated test. It's formatting is nicer than a series of nested ifs, even though functionally that's what it is closer to.

Matthew Schinckel
+14  A: 

There's a pattern that I learned from Twisted Python code.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

You can use it any time you need to dispatch on a token and execute extended piece of code. In a state machine you would have state_ methods, and dispatch on self.state. This switch can be cleanly extended by inheriting from base class and defining your own do_ methods. Often times you won't even have do_ methods in the base class.

Edit: how exactly is that used

In case of SMTP you will receive HELO from the wire. The relevant code (from twisted/mail/smtp.py, modified for our case) looks like this

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

You'll receive ' HELO foo.bar.com ' (or you might get 'QUIT' or 'RCPT TO: foo'). This is tokenized into parts as ['HELO', 'foo.bar.com']. The actual method lookup name is taken from parts[0].

(The original method is also called state_COMMAND, because it uses the same pattern to implement a state machine, i.e. getattr(self, 'state_' + self.mode))

æon
I don't see the benefit from this pattern over just calling the methods directly:SMTP().do_HELO('foo.bar.com')OK, there can be common code in the lookupMethod, but since that also can be overwritten by the subclass I don't see what you gain from the indirection.
Mr Shark
You wouldn't know what method to call in advance, that is to say 'HELO' comes from a variable. i've added usage example to the original post
æon
+1  A: 

The switch statement is just syntactical sugar which is probably why Python doesn't have it. You can use if else statements for this functionality easily.

Like Matthew Schinckel said, you can use if and elif and else.

It is also a simple matter to have "fall-through" capabilities like most switch statements. All you have to do is not use elif.

if x == 1:
    # 1
if x == 2:
    # fall-through
elif x == 3:
    # not fall-through
Switch statements are more than sugar in some languages. In C and C++ in particular, switch statements can be converted to jump tables, resulting in one single comparison (whereas if you have a chain of N if-elses, you'll execute O(N) comparisons).
Tom
You'll also find that it doesn't fall through, if x== 1 ... it will never execute what's in the second if ... since x != 2
Nico
A: 

I would just use if/elif/else statements. I think that it's good enough to replace the switch statement.

miya
+1  A: 

expanding on the "dict as switch" idea. if you want to use a default value for your switch:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
Jeremy Cantrell
I think it's clearer to use .get() on the dict with the default specified. I prefer to leave Exceptions for exceptional circumstances, and it cuts three lines of code and a level of indentation without being obscure.
Chris B.
This **is** an exceptional circumstance. It may or may not be a *rare* circumstance depending on useful, but it's definitely an exception (fall back on `'default'`) from the rule (get something from this dict). By design, Python programs use exceptions at the drop of a hat. That being said, using `get` could potentially make the code a bit nicer.
Mike Graham
+32  A: 

If you'd like defaults you could use the dictionary "get" method:

def f(x):
    return {
        'a': 1,
        'b': 2,
        }.get(x, 9)    # 9 is default if x not found
Nick
What if 'a' and 'b' match 1, and 'c' and 'd' match 2?
John Mee
Nick
great ! implementing now ! :)
Tumbleweed
+3  A: 

A true switch/case in Python is going to be more difficult than a dictionary method or if/elif/else methods because the simple versions do not support fall through. Another downfall of the if/elif/else method is the need for repeated comparisons. The C implementation of a switch/case has a performance benefit over if/else if/else in that only a single comparison is needed. The result of that comparison is used as an offset into a jump table (in the underlying asm generated). To mimicking the true functionality in Python would be a pain. Does any one have an implementation that would allow for fall through while only using a single comparison?

daniel
I suppose that point of view depends on whether you consider fall-through to be a feature or not.
Greg Hewgill
Yes, some languages support switches that do not even allow fall through (C# does not for example, although it does allow multiple mappings, which explains why it still requires "break;")
TM
C# supports explicit fall through.
dalle
Fall through in Python could be solved using recursion.
dalle
He doesn't *need* a switch statement. "I want to write a function in python that returns different fixed values based on the value of an input index." If you need fall through support to do that, you're doing something wrong.
Wallacoloo
A: 
def f(x):    
  return {'a': 1,'b': 2,}.get(x) or "Default"
M. Utku ALTINKAYA
This code could be bug-prone, for instance if you changed the dict to `{'a': 0, 'b': 1}`—the `0` is just as false as the `None` you're trying to test with this `or`. (I avoid ever using the `or` trick for this reason.) The intended use of `get` is `{'a': 1,'b': 2,}.get(x, "Default")`.
Mike Graham
+9  A: 

Python Cookbook has several recipes (implementations and corresponding discussions) for switch statement. Please visit the following links:

  1. http://code.activestate.com/recipes/410692/

  2. http://code.activestate.com/recipes/410695/

  3. http://code.activestate.com/recipes/181064/

bhadra
This deserves more upvotes. In particular, the first is awesome!
Casebash
That first one is clever, but is it Pythonic?
Craig McQueen
+1  A: 

I have made a (relatively) flexible and re-usable solution for this. It can be found at GitHub as this gist. If the result of the switch function is callable, it is automatically called.

+1  A: 

The solutions I use:

A combination of 2 of the solutions posted here, which is relatively easy to read and supports defaults.

result = { 'a': lambda x: x * 5, 'b': lambda x: x + 7, 'c': lambda x: x - 2 }.get(whatToUse, lambda x: x - 22)(value)

where .get('c', lambda x: x - 22)(23) -> looks up "lambda x: x - 2" in the dict and uses it with x=23

.get('xxx', lambda x: x - 22)(44) -> doesn´t find it in the dict and uses the default "lambda x: x - 22" with x=44

+1  A: 

Let's say you don't want to just return a value, but want to use methods that change something on an object. Using the approach stated here would be:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

What happens here is that python evaluates all methods in the dictionary. So even if your value is 'a', the object will get incremented and decremented by x.

Solution:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

So you get a list containing a function and its arguments. This way, only the function pointer and the argument list get returned, not evaluated. 'result' then evaluates the returned function call.

GeeF