我想在Python中编写一个函数,它根据输入索引的值返回不同的固定值.
在其他语言中,我会使用switch
或case
声明,但Python似乎没有switch
声明.在这种情况下,推荐的Python解决方案是什么?
你可以使用字典:
def f(x): return { 'a': 1, 'b': 2, }[x]
如果你想要默认值,你可以使用字典get(key[, default])
方法:
def f(x): return { 'a': 1, 'b': 2 }.get(x, 9) # 9 is default if x not found
我一直喜欢这样做
result = { 'a': lambda x: x * 5, 'b': lambda x: x + 7, 'c': lambda x: x - 2 }[value](x)
从这里
除了字典方法(我非常喜欢BTW)之外,您还可以使用if-elif-else来获取switch/case/default功能:
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
这当然与开关/箱子不同 - 你不能像离开休息那样容易穿透; 声明,但你可以进行更复杂的测试.它的格式比一系列嵌套ifs更好,即使功能上它更接近它.
我最喜欢的开关/案例的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; }
您甚至可以使用元组分配多个变量:
choices = {'a': (1, 2, 3), 'b': (4, 5, 6)} (result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
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))
用法:
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
测试:
n = 2 #Result: #n is an even number. #n is a prime number. n = 11 #Result: #Only single-digit numbers are allowed.
我最喜欢的是一个非常好的食谱.你真的很喜欢它.它是我见过的最接近实际的switch case语句,特别是在功能方面.
这是一个例子:
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
我从Twisted Python代码中学到了一种模式.
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'
您可以在需要分派令牌并执行扩展代码时随时使用它.在状态机中,您将拥有state_
方法并发送self.state
.通过继承基类并定义自己的do_
方法,可以干净地扩展此开关.通常,您甚至不会do_
在基类中使用方法.
编辑:如何使用
如果是SMTP,您将收到HELO
电汇.相关代码(来自twisted/mail/smtp.py
,根据我们的案例修改)看起来像这样
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
你会收到' HELO foo.bar.com '
(或者你可能得到'QUIT'
或'RCPT TO: foo'
).这被标记parts
为['HELO', 'foo.bar.com']
.实际的方法查找名称取自parts[0]
.
(也称为原始方法state_COMMAND
,因为它使用相同的模式来实现状态机,即getattr(self, 'state_' + self.mode)
)
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
假设你不想只返回一个值,而是想使用改变对象上某些东西的方法.使用此处说明的方法将是:
result = { 'a': obj.increment(x), 'b': obj.decrement(x) }.get(value, obj.default(x))
这里发生的是python评估字典中的所有方法.因此,即使您的值为'a',对象也会递增并递减x.
解:
func, args = { 'a' : (obj.increment, (x,)), 'b' : (obj.decrement, (x,)), }.get(value, (obj.default, (x,))) result = func(*args)
所以你得到一个包含函数及其参数的列表.这样,只返回函数指针和参数列表,而不是求值.'result'然后计算返回的函数调用.
我只想把我的两美分放在这里.Python中没有case/switch语句的原因是因为Python遵循'Theres只有一种正确的方法来做某事'的原则.很明显,你可以想出各种方法来重新创建开关/案例功能,但Pythonic实现这一点的方法是if/elif结构.即
if something: return "first thing" elif somethingelse: return "second thing" elif yetanotherthing: return "third thing" else: return "default thing"
我觉得PEP 8值得点头.Python的一个美妙之处在于其简洁和优雅.这很大程度上源于我们在PEP 8中提出的原则,包括"只有一种正确的做法"
扩大"dict as switch"的想法.如果要为交换机使用默认值:
def f(x): try: return { 'a': 1, 'b': 2, }[x] except KeyError: return 'default'
如果您正在搜索extra语句,作为"switch",我构建了一个扩展Python的python模块.它被称为ESPY "Python的增强结构",它可用于Python 2.x和Python 3.x.
例如,在这种情况下,可以通过以下代码执行switch语句:
macro switch(arg1): while True: cont=False val=%arg1% socket case(arg2): if val==%arg2% or cont: cont=True socket socket else: socket break
可以像这样使用:
a=3 switch(a): case(0): print("Zero") case(1): print("Smaller than 2"): break else: print ("greater than 1")
所以espy在Python中将其翻译为:
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
如果您有一个复杂的案例块,您可以考虑使用函数字典查找表...
如果您还没有这样做,那么最好先进入调试器并查看字典如何查找每个函数.
注意:不要不使用"()"的情况下/字典查找内部或它会调用每个函数作为字典/ case块被创建.请记住这一点,因为您只想使用哈希样式查找调用每个函数一次.
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()
我发现了一个常见的开关结构:
switch ...parameter... case p1: v1; break; case p2: v2; break; default: v3;
可以用Python表示如下:
(lambda x: v1 if p1(x) else v2 if p2(x) else v3)
或以更清晰的方式格式化:
(lambda x: v1 if p1(x) else v2 if p2(x) else v3)
python版本不是一个语句,而是一个表达式,它的计算结果为一个值.
我在谷歌搜索的任何地方找不到我想要的简单答案.但无论如何我都想通了.这真的很简单.决定张贴它,也许可以防止别人头上的一些划痕.关键是"in"和元组.这是带有连贯的switch语句行为,包括RANDOM直通.
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. 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.
我使用的解决方案:
这里发布的两个解决方案的组合,相对容易阅读并支持默认值.
result = { 'a': lambda x: x * 5, 'b': lambda x: x + 7, 'c': lambda x: x - 2 }.get(whatToUse, lambda x: x - 22)(value)
哪里
.get('c', lambda x: x - 22)(23)
"lambda x: x - 2"
在字典中查找并使用它x=23
.get('xxx', lambda x: x - 22)(44)
在字典没有找到它,并使用默认"lambda x: x - 22"
用x=44
.
这里的大部分答案都很古老,尤其是被接受的答案,所以看起来值得更新.
首先,官方Python FAQ涵盖了这一点,并elif
为简单案例和dict
更大或更复杂的案例推荐链.它还visit_
为某些情况建议了一组方法(许多服务器框架使用的样式):
def dispatch(self, value): method_name = 'visit_' + str(value) method = getattr(self, method_name) method()
常见问题解答还提到了PEP 275,它是为了获得关于添加C风格切换语句的官方一劳永逸的决定而编写的.但是,PEP实际上被推迟到Python 3,并且它仅被正式拒绝作为单独的提案,PEP 3103.当然,答案是否定的,但如果您对原因或历史感兴趣,则两个PEP可以链接到其他信息.
多次出现的一件事(并且可以在PEP 275中看到,即使它被作为实际建议被删除)是如果你真的被8行代码处理4个案件而烦恼,而6您在C或Bash中拥有的行,您可以随时写下:
if x == 1: print('first') elif x == 2: print('second') elif x == 3: print('third') else: print('did not place')
PEP 8并没有完全鼓励这一点,但它具有可读性,而且不太单一.
自从PEP 3103遭到拒绝以来的十多年里,C风格案例陈述的问题,甚至是Go中稍微强大的版本,都被认为是死的; 每当有人提出python-ideas或-dev时,他们都会参考旧的决定.
然而,每隔几年就会出现完全ML风格模式匹配的想法,特别是因为像Swift和Rust这样的语言已经采用了它.问题在于,如果没有代数数据类型,很难在模式匹配中得到充分利用.虽然Guido一直对这个想法表示同情,但是没有人能够提出一个非常适合Python的提案.(你可以阅读我的2014年稻草人的例子.)这可能会改变dataclass
3.7和一些零星的提议,以更强大enum
的处理和类型,或与各种语句本地绑定的各种提议(如PEP 3150,或目前正在讨论的一系列提案 - -ideas).但到目前为止,它还没有.
对于Perl 6风格的匹配,偶尔也会提出建议,这基本上是从elif
正则表达式到单一调度类型切换的所有内容的混搭.
# 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
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
我做了这个小而干净的解决方案
result = { 'case1': foo1, 'case2': foo2, 'case3': foo3, 'default': default, }.get(option)()
其中foo1(),foo2(),foo3()和default()是函数
我喜欢Mark Bies的回答
由于x
变量必须使用两次,我将lambda函数修改为无参数.
我必须跑 results[value](value)
In [2]: result = { ...: 'a': lambda x: 'A', ...: 'b': lambda x: 'B', ...: 'c': lambda x: 'C' ...: } ...: result['a']('a') ...: Out[2]: 'A' In [3]: result = { ...: 'a': lambda : 'A', ...: 'b': lambda : 'B', ...: 'c': lambda : 'C', ...: None: lambda : 'Nothing else matters' ...: } ...: result['a']() ...: Out[3]: 'A'
编辑:我注意到我可以使用None
带字典的类型.所以这会模仿switch ; case else
def f(x): return 1 if x == 'a' else\ 2 if x in 'bcd' else\ 0 #default
简短易读,具有默认值,并支持条件和返回值中的表达式.
但是,它的效率低于带字典的解决方案.例如,Python必须在返回默认值之前扫描所有条件.
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. ')
打印Was 1. Was 1 or 2. Was something.
(该死!为什么我不能有拖尾的内嵌代码块空白?)如果expression
计算结果为1
,Was 2.
如果expression
计算结果为2
,或者Was something.
如果expression
计算结果为别的东西。
您可以使用调度的字典:
#!/usr/bin/env python def 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()
输出:
This is case 1 This is case 3 This is case 2 This is case 1
定义:
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]() elif None in options: options[None]()
允许我将多个案例映射到同一代码,并提供默认选项:
def sample(x): switch(x, { _: lambda: print("other") for _ in 'cdef' }, { 'a': lambda: print("hello"), 'b': lambda: ( print("goodbye,"), print("!")), None: lambda: print("I dunno") })
每个重复的案例都必须放在自己的字典中;在查找值之前,switch()合并字典。它仍然比我想要的还要难看,但是它具有在表达式上使用哈希查找的基本效率,而不是循环遍历所有键。
我认为最好的方法是使用python语言习语来保持代码的可测试性.如前面的答案所示,我使用字典来利用python结构和语言,并将"case"代码保存在不同的方法中.下面有一个类,但您可以直接使用模块,全局和函数.该类具有可以使用隔离进行测试的方法.根据您的需要,您也可以使用静态方法和属性.
class ChoiceManager: def __init__(self): self.__choice_table = \ { "CHOICE1" : self.my_func1, "CHOICE2" : self.my_func2, } def my_func1(self, data): pass def my_func2(self, data): pass def process(self, case, data): return self.__choice_table[case](data) ChoiceManager().process("CHOICE1", my_data)
可以使用此方法同时使用类作为 "__choice_table"的键.通过这种方式,您可以避免异常滥用并保持所有清洁和可测试.
假设您必须处理来自网络或MQ的大量消息或数据包.每个数据包都有自己的结构和管理代码(以通用方式).使用上面的代码可以执行以下操作:
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)
因此,复杂性不会在代码流中传播,而是在代码结构中呈现.
扩展Greg Hewgill的答案 -我们可以使用装饰器封装字典解决方案:
def case(callable): """switch-case decorator""" class case_class(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs def do_call(self): return callable(*self.args, **self.kwargs) return case_class def switch(key, cases, default=None): """switch-statement""" ret = None try: ret = case[key].do_call() except KeyError: if default: ret = default.do_call() finally: return ret
然后可以与@case
-decorator 一起使用
@case def case_1(arg1): print 'case_1: ', arg1 @case def case_2(arg1, arg2): print 'case_2' return arg1, arg2 @case def default_case(arg1, arg2, arg3): print 'default_case: ', arg1, arg2, arg3 ret = switch(somearg, { 1: case_1('somestring'), 2: case_2(13, 42) }, default_case(123, 'astring', 3.14)) print ret
好消息是,这已经在NeoPySwitch -module中完成。只需使用pip安装:
pip install NeoPySwitch
我倾向于使用也使用字典的解决方案是:
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)()
这样做的好处是,它不必每次都尝试评估函数,而只需确保外部函数获取内部函数所需的所有信息即可。
到目前为止,已经有很多答案说:“我们没有在Python中进行切换,可以这样做”。但是,我想指出的是,switch语句本身是一个易于滥用的构造,在大多数情况下可以并且应该避免使用它们,因为它们会促进惰性编程。例子:
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
现在,您可以使用切换语句来执行此操作(如果Python提供了一个语句),但是您会浪费时间,因为有些方法可以很好地完成此操作。或者,也许您不那么明显:
def ConvertToReason(code): if (code == 200): return 'Okay' elif (code == 400): return 'Bad Request' elif (code == 404): return 'Not Found' else: return None
但是,这种操作可以并且应该用字典来处理,因为它将更快,更简单,更不容易出错并且更紧凑。
switch语句的绝大多数“用例”将属于这两种情况之一;如果您已经彻底考虑了问题,则几乎没有理由使用它。
因此,与其问“我如何切换Python?”,不如问“为什么要切换Python?”。因为这通常是更有趣的问题,并且经常会暴露出您要构建的产品的设计缺陷。
现在,这并不是说也不应该使用任何开关。状态机,词法分析器,解析器和自动机在某种程度上都使用它们,并且通常,当您从对称输入开始并转到非对称输出时,它们可能会有用。您只需要确保不要将开关用作锤子,因为在代码中会看到很多钉子。