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?
if x =='a':# Do the thingelif x =='b':# Do the other thingif x in'bc':# Fall-through by not using elif, but now the default case includes case 'a'!elif x in'xyz':# Do yet another thingelse:# Do the default
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
if x in 'bc':
# Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
# Do yet another 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. Its formatting is nicer than a series of nested ifs, even though functionally that’s what it is closer to.
回答 4
我最喜欢的用于switch / case的Python配方是:
choices ={'a':1,'b':2}
result = choices.get(key,'default')
简单的场景简单明了。
比较11行以上的C代码:
// C Language version of a simple 'switch/case'.
switch( key ){
case 'a':
result =1;break;
case 'b':
result =2;break;
default :
result =-1;}
class switch(object):
value =Nonedef __new__(class_, value):
class_.value = value
returnTruedef case(*args):return any((arg == switch.value for arg in args))
用法:
while switch(n):if case(0):print"You typed zero."breakif case(1,4,9):print"n is a perfect square."breakif case(2):print"n is an even number."if case(2,3,5,7):print"n is a prime number."breakif case(6,8):print"n is an even number."breakprint"Only single-digit numbers are allowed."break
测试:
n =2#Result:#n is an even number.#n is a prime number.
n =11#Result:#Only single-digit numbers are allowed.
class switch(object):
value = None
def __new__(class_, value):
class_.value = value
return True
def case(*args):
return any((arg == switch.value for arg in args))
Usage:
while switch(n):
if case(0):
print "You typed zero."
break
if case(1, 4, 9):
print "n is a perfect square."
break
if case(2):
print "n is an even number."
if case(2, 3, 5, 7):
print "n is a prime number."
break
if case(6, 8):
print "n is an even number."
break
print "Only single-digit numbers are allowed."
break
Tests:
n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
class switch(object):def __init__(self, value):
self.value = value
self.fall =Falsedef __iter__(self):"""Return the match method once, then stop"""yield self.match
raiseStopIterationdef match(self,*args):"""Indicate whether or not to enter a case suite"""if self.fall ornot args:returnTrueelif self.value in args:# changed for v1.5, see below
self.fall =TruereturnTrueelse:returnFalse
这是一个例子:
# The following example is pretty much the exact use-case of a dictionary,# but is included for its simplicity. Note that you can include statements# in each suite.
v ='ten'for case in switch(v):if case('one'):print1breakif case('two'):print2breakif case('ten'):print10breakif case('eleven'):print11breakif case():# default, could also just omit condition or 'if True'print"something else!"# No need to break here, it'll stop anyway# break is used here to look as much like the real thing as possible, but# elif is generally just as good and more concise.# Empty suites are considered syntax errors, so intentional fall-throughs# should contain 'pass'
c ='z'for case in switch(c):if case('a'):pass# only necessary if the rest of the suite is emptyif case('b'):pass# ...if case('y'):passif case('z'):print"c is lowercase!"breakif case('A'):pass# ...if case('Z'):print"c is uppercase!"breakif case():# defaultprint"I dunno what c was!"# As suggested by Pierre Quentel, you can even expand upon the# functionality of the classic 'case' statement by matching multiple# cases in a single shot. This greatly benefits operations such as the# uppercase/lowercase example above:import string
c ='A'for case in switch(c):if case(*string.lowercase):# note the * for unpacking as argumentsprint"c is lowercase!"breakif case(*string.uppercase):print"c is uppercase!"breakif case('!','?','.'):# normal argument passing style also appliesprint"c is a sentence terminator!"breakif case():# defaultprint"I dunno what c was!"
My favorite one is a really nice recipe. You’ll really like it. It’s the closest one I’ve seen to actual switch case statements, especially in features.
class switch(object):
def __init__(self, value):
self.value = value
self.fall = False
def __iter__(self):
"""Return the match method once, then stop"""
yield self.match
raise StopIteration
def match(self, *args):
"""Indicate whether or not to enter a case suite"""
if self.fall or not args:
return True
elif self.value in args: # changed for v1.5, see below
self.fall = True
return True
else:
return False
Here’s an example:
# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
if case('one'):
print 1
break
if case('two'):
print 2
break
if case('ten'):
print 10
break
if case('eleven'):
print 11
break
if case(): # default, could also just omit condition or 'if True'
print "something else!"
# No need to break here, it'll stop anyway
# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.
# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
if case('a'): pass # only necessary if the rest of the suite is empty
if case('b'): pass
# ...
if case('y'): pass
if case('z'):
print "c is lowercase!"
break
if case('A'): pass
# ...
if case('Z'):
print "c is uppercase!"
break
if case(): # default
print "I dunno what c was!"
# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
if case(*string.lowercase): # note the * for unpacking as arguments
print "c is lowercase!"
break
if case(*string.uppercase):
print "c is uppercase!"
break
if case('!', '?', '.'): # normal argument passing style also applies
print "c is a sentence terminator!"
break
if case(): # default
print "I dunno what c was!"
回答 7
classSwitch:def __init__(self, value):
self.value = value
def __enter__(self):return self
def __exit__(self, type, value, traceback):returnFalse# Allows a traceback to occurdef __call__(self,*values):return self.value in values
from datetime import datetime
withSwitch(datetime.today().weekday())as case:if case(0):# Basic usage of switchprint("I hate mondays so much.")# Note there is no break needed hereelif case(1,2):# This switch also supports multiple conditions (in one line)print("When is the weekend going to be here?")elif case(3,4):print("The weekend is near.")else:# Default would occur hereprint("Let's go have fun!")# Didn't use case for example purposes
class Switch:
def __init__(self, value):
self.value = value
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
return False # Allows a traceback to occur
def __call__(self, *values):
return self.value in values
from datetime import datetime
with Switch(datetime.today().weekday()) as case:
if case(0):
# Basic usage of switch
print("I hate mondays so much.")
# Note there is no break needed here
elif case(1,2):
# This switch also supports multiple conditions (in one line)
print("When is the weekend going to be here?")
elif case(3,4):
print("The weekend is near.")
else:
# Default would occur here
print("Let's go have fun!") # Didn't use case for example purposes
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))
回答 9
假设您不想只返回一个值,而是想使用更改对象上某些内容的方法。使用此处说明的方法将是:
result ={'a': obj.increment(x),'b': obj.decrement(x)}.get(value, obj.default(x))
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.
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.
I’m just going to drop my two cents in here. The reason there isn’t a case/switch statement in Python is because Python follows the principle of ‘Theres only one right way to do something’. So obviously you could come up with various ways of recreating switch/case functionality, but the Pythonic way of accomplishing this is the if/elif construct. ie
I just felt PEP 8 deserved a nod here. One of the beautiful things about Python is its simplicity and elegance. That is largely derived from principles laid our in PEP 8, including “There’s only one right way to do something”
def first_case():print"first"def second_case():print"second"def third_case():print"third"
mycase ={'first': first_case,#do not use ()'second': second_case,#do not use ()'third': third_case #do not use ()}
myfunc = mycase['first']
myfunc()
If you have a complicated case block you can consider using a function dictionary lookup table…
If you haven’t done this before its a good idea to step into your debugger and view exactly how the dictionary looks up each function.
NOTE: Do not use “()” inside the case/dictionary lookup or it will call each of your functions as the dictionary / case block is created. Remember this because you only want to call each function once using a hash style lookup.
def first_case():
print "first"
def second_case():
print "second"
def third_case():
print "third"
mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()
If you’re searching extra-statement, as “switch”, I built a python module that extends Python. It’s called ESPY as “Enhanced Structure for Python” and it’s available for both Python 2.x and Python 3.x.
For example, in this case, a switch statement could be performed by the following code:
macro switch(arg1):
while True:
cont=False
val=%arg1%
socket case(arg2):
if val==%arg2% or cont:
cont=True
socket
socket else:
socket
break
that can be used like this:
a=3
switch(a):
case(0):
print("Zero")
case(1):
print("Smaller than 2"):
break
else:
print ("greater than 1")
so espy translate it in Python as:
a=3
while True:
cont=False
if a==0 or cont:
cont=True
print ("Zero")
if a==1 or cont:
cont=True
print ("Smaller than 2")
break
print ("greater than 1")
break
回答 14
我发现了一个常见的开关结构:
switch ...parameter...
case p1: v1;break;
case p2: v2;break;
default: v3;
Most of the answers here are pretty old, and especially the accepted ones, so it seems worth updating.
First, the official Python FAQ covers this, and recommends the elif chain for simple cases and the dict for larger or more complex cases. It also suggests a set of visit_ methods (a style used by many server frameworks) for some cases:
The FAQ also mentions PEP 275, which was written to get an official once-and-for-all decision on adding C-style switch statements. But that PEP was actually deferred to Python 3, and it was only officially rejected as a separate proposal, PEP 3103. The answer was, of course, no—but the two PEPs have links to additional information if you’re interested in the reasons or the history.
One thing that came up multiple times (and can be seen in PEP 275, even though it was cut out as an actual recommendation) is that if you’re really bothered by having 8 lines of code to handle 4 cases, vs. the 6 lines you’d have in C or Bash, you can always write this:
if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')
This isn’t exactly encouraged by PEP 8, but it’s readable and not too unidiomatic.
Over the more than a decade since PEP 3103 was rejected, the issue of C-style case statements, or even the slightly more powerful version in Go, has been considered dead; whenever anyone brings it up on python-ideas or -dev, they’re referred to the old decision.
However, the idea of full ML-style pattern matching arises every few years, especially since languages like Swift and Rust have adopted it. The problem is that it’s hard to get much use out of pattern matching without algebraic data types. While Guido has been sympathetic to the idea, nobody’s come up with a proposal that fits into Python very well. (You can read my 2014 strawman for an example.) This could change with dataclass in 3.7 and some sporadic proposals for a more powerful enum to handle sum types, or with various proposals for different kinds of statement-local bindings (like PEP 3150, or the set of proposals currently being discussed on -ideas). But so far, it hasn’t.
There are also occasionally proposals for Perl 6-style matching, which is basically a mishmash of everything from elif to regex to single-dispatch type-switching.
回答 16
运行功能的解决方案:
result ={'case1': foo1,'case2': foo2,'case3': foo3,'default': default,}.get(option)()
l =['Dog','Cat','Bird','Bigfoot','Dragonfly','Snake','Bat','Loch Ness Monster']for x in l:if x in('Dog','Cat'):
x +=" has four legs"elif x in('Bat','Bird','Dragonfly'):
x +=" has wings."elif x in('Snake',):
x +=" has a forked tongue."else:
x +=" is a big mystery by default."print(x)print()for x in range(10):if x in(0,1):
x ="Values 0 and 1 caught here."elif x in(2,):
x ="Value 2 caught here."elif x in(3,7,8):
x ="Values 3, 7, 8 caught here."elif x in(4,6):
x ="Values 4 and 6 caught here"else:
x ="Values 5 and 9 caught in default."print(x)
提供:
Dog has four legs
Cat has four legs
Bird has wings.Bigfootis a big mystery by default.Dragonfly has wings.Snake has a forked tongue.Bat has wings.LochNessMonsteris a big mystery by default.Values0and1 caught here.Values0and1 caught here.Value2 caught here.Values3,7,8 caught here.Values4and6 caught here
Values5and9 caught in default.Values4and6 caught here
Values3,7,8 caught here.Values3,7,8 caught here.Values5and9 caught in default.
I didn’t find the simple answer I was looking for anywhere on Google search. But I figured it out anyway. It’s really quite simple. Decided to post it, and maybe prevent a few less scratches on someone else’s head. The key is simply “in” and tuples. Here is the switch statement behavior with fall-through, including RANDOM fall-through.
l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']
for x in l:
if x in ('Dog', 'Cat'):
x += " has four legs"
elif x in ('Bat', 'Bird', 'Dragonfly'):
x += " has wings."
elif x in ('Snake',):
x += " has a forked tongue."
else:
x += " is a big mystery by default."
print(x)
print()
for x in range(10):
if x in (0, 1):
x = "Values 0 and 1 caught here."
elif x in (2,):
x = "Value 2 caught here."
elif x in (3, 7, 8):
x = "Values 3, 7, 8 caught here."
elif x in (4, 6):
x = "Values 4 and 6 caught here"
else:
x = "Values 5 and 9 caught in default."
print(x)
Provides:
Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.
Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
回答 18
我使用的解决方案:
此处发布的两种解决方案的组合,相对易于阅读并支持默认设置。
result ={'a':lambda x: x *5,'b':lambda x: x +7,'c':lambda x: x -2}.get(whatToUse,lambda x: x -22)(value)
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.
回答 19
# simple case alternative
some_value =5.0# this while loop block simulates a case block# casewhileTrue:# case 1if some_value >5:print('Greater than five')break# case 2if some_value ==5:print('Equal to five')break# else case 3print('Must be less than 5')break
# simple case alternative
some_value = 5.0
# this while loop block simulates a case block
# case
while True:
# case 1
if some_value > 5:
print ('Greater than five')
break
# case 2
if some_value == 5:
print ('Equal to five')
break
# else case 3
print ( 'Must be less than 5')
break
回答 20
def f(x):
dictionary ={'a':1,'b':2,'c':3}return dictionary.get(x,'Not Found')##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary
def f(x):
dictionary = {'a':1, 'b':2, 'c':3}
return dictionary.get(x,'Not Found')
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
Short and easy to read, has a default value and supports expressions in both conditions and return values.
However, it is less efficient than the solution with a dictionary. For example, Python has to scan through all the conditions before returning the default value.
回答 23
您可以使用调度的字典:
#!/usr/bin/env pythondef case1():print("This is case 1")def case2():print("This is case 2")def case3():print("This is case 3")
token_dict ={"case1": case1,"case2": case2,"case3": case3,}def main():
cases =("case1","case3","case2","case1")for case in cases:
token_dict[case]()if __name__ =='__main__':
main()
输出:
Thisis case 1Thisis case 3Thisis case 2Thisis case 1
for case in[expression]:if case ==1:print(end='Was 1. ')if case ==2:print(end='Was 2. ')breakif case in(1,2):print(end='Was 1 or 2. ')print(end='Was something. ')
打印Was 1. Was 1 or 2. Was something.(该死!为什么我不能有拖尾的内嵌代码块空白?)如果expression计算结果为1,Was 2.如果expression计算结果为2,或者Was something.如果expression计算结果为别的东西。
Simple, not tested; each condition is evaluated independently: there is no fall-through, but all cases are evaluated (although the expression to switch on is only evaluated once), unless there is a break statement. For example,
for case in [expression]:
if case == 1:
print(end='Was 1. ')
if case == 2:
print(end='Was 2. ')
break
if case in (1, 2):
print(end='Was 1 or 2. ')
print(end='Was something. ')
prints Was 1. Was 1 or 2. Was something.(Dammit! Why can’t I have trailing whitespace in inline code blocks?) if expression evaluates to 1, Was 2. if expression evaluates to 2, or Was something. if expression evaluates to something else.
回答 25
定义:
def switch1(value, options):if value in options:
options[value]()
允许您使用相当简单的语法,将案例捆绑到地图中:
def sample1(x):
local ='betty'
switch1(x,{'a':lambda:print("hello"),'b':lambda:(print("goodbye,"+ local),print("!")),})
我一直试图以一种让我摆脱“ lambda:”的方式重新定义开关,但是放弃了。调整定义:
def switch(value,*maps):
options ={}for m in maps:
options.update(m)if value in options:
options[value]()elifNonein options:
options[None]()
Each replicated case has to be in its own dictionary; switch() consolidates the dictionaries before looking up the value. It’s still uglier than I’d like, but it has the basic efficiency of using a hashed lookup on the expression, rather than a loop through all the keys.
classPacketManager:def __init__(self):
self.__choice_table = \
{ControlMessage: self.my_func1,DiagnosticMessage: self.my_func2,}def my_func1(self, data):# process the control message herepassdef my_func2(self, data):# process the diagnostic message herepassdef process(self, pkt):return self.__choice_table[pkt.__class__](pkt)
pkt =GetMyPacketFromNet()PacketManager().process(pkt)# isolated test or isolated usage exampledef test_control_packet():
p =ControlMessage()PacketManager().my_func1(p)
I think the best way is to use the python language idioms to keep your code testable. As showed in previous answers, I use dictionaries to take advantage of python structures and language and keep the “case” code isolated in different methods. Below there is a class, but you can use directly a module, globals and functions. The class has methods that can be tested with isolation.
Dependending to your needs, you can play with static methods and attributes too.
It is possible to take advantage of this method using also classes as keys of “__choice_table”. In this way you can avoid isinstance abuse and keep all clean and testable.
Supposing you have to process a lot of messages or packets from the net or your MQ. Every packet has its own structure and its management code (in a generic way).
With the above code it is possible to do something like this:
class PacketManager:
def __init__(self):
self.__choice_table = \
{
ControlMessage : self.my_func1,
DiagnosticMessage : self.my_func2,
}
def my_func1(self, data):
# process the control message here
pass
def my_func2(self, data):
# process the diagnostic message here
pass
def process(self, pkt):
return self.__choice_table[pkt.__class__](pkt)
pkt = GetMyPacketFromNet()
PacketManager().process(pkt)
# isolated test or isolated usage example
def test_control_packet():
p = ControlMessage()
PacketManager().my_func1(p)
So complexity is not spread in the code flow but it is rendered in code structure.
The good news are that this has already been done in NeoPySwitch-module. Simply install using pip:
pip install NeoPySwitch
回答 28
我倾向于使用也使用字典的解决方案是:
def decision_time( key,*args,**kwargs):def action1()"""This function is a closure - and has access to all the arguments"""passdef action2()"""This function is a closure - and has access to all the arguments"""passdef action3()"""This function is a closure - and has access to all the arguments"""passreturn{1:action1,2:action2,3:action3}.get(key,default)()
A solution I tend to use which also makes use of dictionaries is :
def decision_time( key, *args, **kwargs):
def action1()
"""This function is a closure - and has access to all the arguments"""
pass
def action2()
"""This function is a closure - and has access to all the arguments"""
pass
def action3()
"""This function is a closure - and has access to all the arguments"""
pass
return {1:action1, 2:action2, 3:action3}.get(key,default)()
This has the advantage that it doesn’t try to evaluate the functions every time, and you just have to ensure that the outer function gets all the information that the inner functions need.
There have been a lot of answers so far that have said, “we don’t have a switch in Python, do it this way”. However, I would like to point out that the switch statement itself is an easily-abused construct that can and should be avoided in most cases because they promote lazy programming. Case in point:
def ToUpper(lcChar):
if (lcChar == 'a' or lcChar == 'A'):
return 'A'
elif (lcChar == 'b' or lcChar == 'B'):
return 'B'
...
elif (lcChar == 'z' or lcChar == 'Z'):
return 'Z'
else:
return None # or something
Now, you could do this with a switch-statement (if Python offered one) but you’d be wasting your time because there are methods that do this just fine. Or maybe, you have something less obvious:
However, this sort of operation can and should be handled with a dictionary because it will be faster, less complex, less prone to error and more compact.
And the vast majority of “use cases” for switch statements will fall into one of these two cases; there’s just very little reason to use one if you’ve thought about your problem thoroughly.
So, rather than asking “how do I switch in Python?”, perhaps we should ask, “why do I want to switch in Python?” because that’s often the more interesting question and will often expose flaws in the design of whatever you’re building.
Now, that isn’t to say that switches should never be used either. State machines, lexers, parsers and automata all use them to some degree and, in general, when you start from a symmetrical input and go to an asymmetrical output they can be useful; you just need to make sure that you don’t use the switch as a hammer because you see a bunch of nails in your code.