Welcome, guest | Sign In | My Account | Store | Cart

Python style switches. Showing several ways of making a 'switch' in python.

Python, 101 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#==================================================
#   1. Select non-function values:
#==================================================

location = 'myHome'  
fileLocation = {'myHome'   :path1,
                'myOffice' :path2,
                'somewhere':path3}[location]

#==================================================
#   2. Select functions:
#==================================================

functionName = input('Enter a function name')
eval('%s()'%functionName)

#==================================================
#   3. Select values with 'range comparisons':
#==================================================
#
# Say, we have a range of values, like: [0,1,2,3]. You want to get a
# specific value when x falls into a specific range:
#
# x<0   : 'return None'
# 0<=x<1: 'return 1'
# 1<=x<2: 'return 2'
# 2<=x<3: 'return 3'
# 3<=x  : 'return None'
# 
# It is eazy to construct a switch by simply making the above rules
# into a dictionary:

selector={ 
   x<0    : 'return None',
   0<=x<1 : 'return 1',
   1<=x<2 : 'return 2',
   2<=x<3 : 'return 3',
   3<=x   : 'return None'
   }[1]  

# During the construction of the selector, any given x will turn the
# selector into a 2-element dictionary:

selector={ 
   0 : 'return None',
   1 : #(return something you want),
   }[1]  

# This is very useful in places where a selection is to be made upon any
# true/false decision. One more example:

selector={
  type(x)==str  : "it's a str",
  type(x)==tuple: "it's a tuple",
  type(x)==dict : "it's a dict"
  } [1]

#==================================================
#   4. Select functions with 'range comparisons':
#==================================================
#
# You want to execute a specific function when x falls into a specific range:

functionName={
   x<0   : setup,
   0<=x<1: loadFiles,
   1<=x<2: importModules
   }[1]  

functionName()

#==================================================
#   5. and/or style
#==================================================
#
# x = a and 'a' or None
# 
# is same as:
#
# if a: x = 'a'
# else: x = None
# 
# More example: a switch in Basic:
#

Select Case x
   Case x<0    : y = -1 
   Case 0<=x<1 : y =  0
   Case 1<=x<2 : y =  1
   Case 2<=x<3 : y =  2
   Case Else   : y =  'n/a'
End Select  

# 
# in Python
#

y = ( (x<0)    and -1 ) or \
    ( (0<=x<1) and  0 ) or \
    ( (1<=x<2) and  1 ) or \
    ( (2<=x<3) and  2 ) or 'n/a'

  

Be careful when selecting functions using the 'dictionary-based' switches :

If it is constructed this way:

aFunction={ x<0 : setup(), 0<=x<1: loadFiles(), 1<=x<2: importModules() }[1]

then all 3 functions will be executed during the construction of aFunction.

12 comments

Runsun Pan (author) 18 years, 3 months ago  # | flag

A switch example in a 'Rock, Scissors, Paper' game. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/181064

import random
userPick=''
while userPick not in ['r', 'p', 's']:
     userPick = raw_input('\tPlease enter (r)ock, (p)aper or (s)cissors to play... ')[0]

computerPick= random.choice(['r','p','s'])
pair = (userPick, computerPick)

result= { pair==('r','r') or pair==('p','p') or pair==('s','s'): 'draw!',
          pair==('p','r') or pair==('s','p') or pair==('r','s'): 'You won!',
          pair==('r','p') or pair==('p','s') or pair==('s','r'): 'Computer won!'}[1]

print 'You entered: ', userPick, ', Computer entered: ', computerPick
print result
Runsun Pan (author) 18 years, 3 months ago  # | flag

A switch example in a 'Rock, Scissors, Paper' game. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/181064

import random
userPick=''
while userPick not in ['r', 'p', 's']:
     userPick = raw_input('\tPlease enter (r)ock, (p)aper or (s)cissors to play... ')[0]

computerPick= random.choice(['r','p','s'])
pair = (userPick, computerPick)

result= { pair==('r','r') or pair==('p','p') or pair==('s','s'): 'draw!',
          pair==('p','r') or pair==('s','p') or pair==('r','s'): 'You won!',
          pair==('r','p') or pair==('p','s') or pair==('s','r'): 'Computer won!'}[1]

print 'You entered: ', userPick, ', Computer entered: ', computerPick
print result
Hamish Lawson 18 years, 3 months ago  # | flag

No need to use eval. You give the need not to call each function while constructing the dictionary as the reason for using strings containing the function names and eval(). However functions are first-class objects that you can pass around - they only get called when you use the () operator on them. Thus you can instead write:

function = {
   x&lt;0   : setup,
   0&lt;=x&lt;1: loadFiles,
   1&lt;=x&lt;2: importModules
   }[1]

function()

(For Python 2.2.1 and later it would also probably be more readable to use True rather than 1 as the index for the dictionary.)

Runsun Pan (author) 18 years, 3 months ago  # | flag

Thx for the input. The code was modified accordingly.

Hamish Lawson 18 years, 3 months ago  # | flag

You're welcome ... ... but just a further small point of style: since the values in the dictionary are no longer the names of functions but references to the functions themselves, perhaps 'functionName' is now a less appropriate variable name - hence my suggestion of 'function'.

Martin Miller 18 years, 3 months ago  # | flag

Relatively high penalty for doing range comparisons. Compared to an if/elif equivalent, there's quite a bit of overhead involved because, in addition to the construction of the dictionary itself, all of the condition tests are executed during the process.

Performance testing using a large number of random input values indicate than the technique is about 66-70% slower than using a simple if/elif series of logical expressions.

Whether or not the speed is important, of course, depends on many other factors and would need to be traded-off against the arguably cleaner-looking syntax.

As for the other proposed uses (selecting function and non-fuction values) -- isn't that exactly the purpose of dictionary/mapping objects? Although using temporarily constructed ones in this manor is clever, I suppose.

Runsun Pan (author) 18 years ago  # | flag

Some discussions about python switches: http://simon.incutio.com/archive/2004/05/07/switch#comments

Runsun Pan (author) 18 years ago  # | flag

Some discussions about python switches: http://simon.incutio.com/archive/2004/05/07/switch#comments

Qiangning Hong 18 years ago  # | flag

dangerous in and/or style! In and/or style, at the position between 'and' and 'or', you cannot use 0 or any other values which python consider as False in boolean expression.

In your example:

y = ( (x&lt;0)    and -1 ) or \
    ( (0&lt;=x&lt;1) and  0 ) or \
    ( (1&lt;=x&lt;2) and  1 ) or \
    ( (2&lt;=x&lt;3) and  2 ) or 'n/a'

y will get the wrong 'n/a' when x equals 0.5, not 0, the correct answer.

Samuel Reynolds 17 years, 10 months ago  # | flag

6. Dispatch style. As long as the switch parameter contains no characters that are invalid in an identifier:

funcName = 'do_%s' % x
func = getattr( self, funcName, None )
assert func, "Invalid switch value '%s'" % x
func()

...with a separate method for each option:

def do_A(self):
    pass

def do_B(self):
    pass

def do_C(self):
    pass
Andreas Kloss 17 years, 8 months ago  # | flag

A functional approach: Since I am a fan of functional style, I always have the following cond function handy:

def cond(*args):
    '''Lisp- (or Arc-) like cond.
    if tests and functions should be deferred (that is, not
    evaluated anyway), they have to be preceded with "lambda :"
    Use as follows:

    >>> # Normal lisp-style
    >>> cond(False, "Hello", False, "World", True, "ok", False)
    'ok'
    >>> cond(False, 'Hello', 'ok') # if single value, use as default.
    'ok'
    >>> cond(True, "ok", {}[1], "This is a KeyError")
    Traceback (innermost last):
    File "", line 1, in ?
    KeyError: 1
    >>> cond(True, "ok", (lambda : {}[1]), "This is not")
    'ok'
    '''
    pairs = lambda l: l[1:] and ((l[0], l[1]),) + pairs(l[2:]) or l and ((True, l[0]),)
    for test, result in pairs(args):
        if callable(test): test = test()
        if test:
            if callable(result): result = result()
            return result

The parenthesis allows us to write stuff like

cond(
    lambda:x&gt;0, 1,
    lambda:x&lt;0, -1,
    0)

which is IMHO quite readable. And in this example, if x>0, it does not even evaluate the other branch. Thanks, lambda!

runsun pan 6 years, 4 months ago  # | flag

Some other approaches and discussions:

Replacements for switch statement in Python?

http://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python/34639742#34639742

Created by Runsun Pan on Tue, 17 Feb 2004 (PSF)
Python recipes (4591)
Runsun Pan's recipes (8)

Required Modules

  • (none specified)

Other Information and Tasks