ActiveState Powered by ActiveState

Recipe 269708: Some python style switches


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

Python
  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'

  

Discussion

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.

Comments

  1. 1. At 4:06 p.m. on 17 feb 2004, Runsun Pan (the author) said:

    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
    
  2. 2. At 4:06 p.m. on 17 feb 2004, Runsun Pan (the author) said:

    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
    
  3. 3. At 6:04 p.m. on 17 feb 2004, Hamish Lawson said:

    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.)

  4. 4. At 6:41 p.m. on 17 feb 2004, Runsun Pan (the author) said:

    Thx for the input. The code was modified accordingly.

  5. 5. At 6:14 a.m. on 18 feb 2004, Hamish Lawson said:

    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'.

  6. 6. At 12:23 p.m. on 21 feb 2004, Martin Miller said:

    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.

  7. 7. At 2:45 p.m. on 11 may 2004, Runsun Pan (the author) said:

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

  8. 8. At 2:45 p.m. on 11 may 2004, Runsun Pan (the author) said:

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

  9. 9. At 8 p.m. on 12 may 2004, Qiangning Hong said:

    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.

  10. 10. At 7:22 a.m. on 8 jul 2004, Samuel Reynolds said:

    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
    
  11. 11. At 6:02 a.m. on 13 sep 2004, Andreas Kloss said:

    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!

Sign in to comment