在Python中replaceswitch语句?
我想用Python编写一个函数,它根据input索引的值返回不同的固定值。
在其他语言中,我会使用switch
或case
语句,但是Python似乎没有switch
语句。 在这种情况下推荐的Python解决scheme是什么?
你可以用字典:
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来获得开关/大小写/默认function:
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
这当然是不一样的开关/情况 – 你不能像脱离rest一样容易失败; 声明,但你可以有一个更复杂的testing。 它的格式比一系列嵌套ifs更好,即使在function上也是如此。
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
testing:
n = 2 #Result: #n is an even number. #n is a prime number. n = 11 #Result: #Only single-digit numbers are allowed.
我最喜欢的开关/案例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; }
你甚至可以通过使用元组来分配多个variables:
choices = {'a': (1, 2, 3), 'b': (4, 5, 6)} (result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
我从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
上self.state
。 这个开关可以通过从基类inheritance和定义你自己的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'
)。 这被标记为['HELO', 'foo.bar.com']
。 实际的方法查找名称取自parts[0]
。
(原来的方法也被称为state_COMMAND
,因为它使用相同的模式来实现一个状态机,即getattr(self, 'state_' + self.mode)
)
我最喜欢的是一个非常好的食谱 。 你真的会喜欢它。 这是我见过的最接近实际开关的情况下,特别是在function。
这是一个例子:
# 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!"
比方说,你不想只返回一个值,而是想使用改变对象的方法。 使用这里陈述的方法将是:
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'然后评估返回的函数调用。
class Switch: def __init__(self, value): self._val = value def __enter__(self): return self def __exit__(self, type, value, traceback): return False # Allows traceback to occur def __call__(self, cond, *mconds): return self._val in (cond,)+mconds 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
扩大“字典切换”的想法。 如果您想为交换机使用默认值:
def f(x): try: return { 'a': 1, 'b': 2, }[x] except KeyError: return 'default'
如果你正在寻找额外的语句,作为“开关”,我build立了一个扩展Python的Python模块。 它被称为“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
如果你有一个复杂的大小写块,你可以考虑使用函数字典查找表…
如果你还没有做到这一点,那么先进入你的debugging器,然后仔细查看字典是如何查找每个函数的。
注:不要在大小写/字典查找中使用“()”,否则会在创build字典/大小写块时调用每个函数。 请记住这一点,因为您只想使用哈希样式查找来调用每个函数。
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()
我没有find我在谷歌search的任何地方寻找简单的答案。 但无论如何我都明白了 这真的很简单。 决定张贴,也许可以防止less一些别人的头部划痕。 关键是简单的“进”和元组。 这里是switch语句的行为,包括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)
规定:
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.
我发现一个普通的开关结构:
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版本不是一个语句,而是一个expression式,它的值是一个值。
我使用的解决scheme:
这里发布的两个解决scheme的组合,这是相对容易阅读和支持默认值。
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)
在dict中找不到它,并使用x=44
的默认"lambda x: x - 22"
。
# 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
我喜欢Mark Bies的回答
由于x
variables必须使用两次,所以我将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
types。 所以这将模拟switch ; case else
switch ; case else
我只是在这里放下我的两分钱。 Python中没有case / switch语句的原因是因为Python遵循“Theres只有一个正确的方式去做某事”的原则。 所以显然你可以想出各种方式来重新创build开关/shellfunction,但实现这一点的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中的原则,包括“只有一个正确的做法”
我为此做了一个(相对)灵活和可重用的解决scheme。 它可以在GitHub上find这个要点 。 如果开关函数的结果是可调用的,则自动调用。
def f(x): return 1 if x == 'a' else\ 2 if x in 'bcd' else\ 0 #default
简单易读,具有默认值,并支持在条件和返回值中的expression式。
但是,它比字典解决scheme效率低。 例如,Python必须在返回默认值之前扫描所有条件。
定义:
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()在查找值之前合并字典。 它仍然比我想要的更丑,但它具有在expression式上使用散列查找的基本效率,而不是遍历所有键的循环。
我认为最好的方法是使用Python语言成语来保持您的代码可testing性 。 正如前面的答案所示,我使用字典来利用python结构和语言 ,并将“case”代码分隔成不同的方法。 下面是一个类,但是你可以直接使用一个模块,全局variables和函数。 该类具有可以隔离testing的方法。 根据您的需要,您也可以使用静态方法和属性。
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”的键 。 这样可以避免isinstance滥用,并保持所有的清洁和可testing。
假设您必须处理来自networking或您的MQ的大量消息或数据包。 每个数据包都有自己的结构和pipe理代码(通用方式)。 有了上面的代码,可以做这样的事情:
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)
所以复杂性并不在代码stream中传播,而是以代码结构呈现 。
如果您不担心在案例套件中丢失语法高亮,则可以执行以下操作:
exec { 1: """ print ('one') """, 2: """ print ('two') """, 3: """ print ('three') """, }.get(value, """ print ('None') """)
Where value
is the value. In C, this would be:
switch (value) { case 1: printf("one"); break; case 2: printf("two"); break; case 3: printf("three"); break; default: printf("None"); break; }
We can also create a helper function to do this:
def switch(value, cases, default): exec cases.get(value, default)
So we can use it like this for the example with one, two and three:
switch(value, { 1: """ print ('one') """, 2: """ print ('two') """, 3: """ print ('three') """, }, """ print ('None') """)
Expanding on Greg Hewgill's answer – We can encapsulate the dictionary-solution using a decorator:
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
This can then be used with the @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
The good news are that this has already been done in NeoPySwitch -module. Simply install using pip:
pip install NeoPySwitch
I would just use if/elif/else statements. I think that it's good enough to replace the switch statement.
Just mapping some a key to some code is not really and issue as most people have shown using the dict. The real trick is trying to emulate the whole drop through and break thing. I don't think I've ever written a case statement where I used that "feature". Here's a go at drop through.
def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False) case([ (False, lambda:print(5)), (True, lambda:print(4)) ])
I was really imagining it as a single statement. I hope you'll pardon the silly formatting.
reduce( initializer=False, function=(lambda b, f: ( b | f[0] , { False: (lambda:None) , True : f[1] }[b | f[0]]() )[0] ), iterable=[ (False, lambda:print(5)), (True, lambda:print(4)) ] )
I hope that's valid python. It should give you drop through. of course the boolean checks could be expressions and if you wanted them to be evaluated lazily you could wrap them all in a lambda. I wouldn't be to hard to make it accept after executing some of the items in the list either. Just make the tuple (bool, bool, function) where the second bool indicates whether or not to break or drop through.
For the sake of completeness, here are some of my attempts back in stone-age:
http://code.activestate.com/recipes/269708-some-python-style-switches/?in=user-1521341
I especially enjoy the use of "3. Select values with 'range comparisons'"
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 .
I was quite confused after reading the answer, but this cleared it all up:
def numbers_to_strings(argument): switcher = { 0: "zero", 1: "one", 2: "two", } return switcher.get(argument, "nothing")
This code is analogous to:
function(argument){ switch(argument) { case 0: return "zero"; case 1: return "one"; case 2: return "two"; default: return "nothing"; } }
Check the Source for more about dictionary mapping to functions.