Python中替代switch语句的方法是什么?

1717

我想在Python中编写一个函数,根据输入索引的值返回不同的固定值。

在其他语言中,我会使用switchcase语句,但Python似乎没有switch语句。在这种情况下,有哪些推荐的Python解决方案?


77
相关PEP,由Guido本人撰写:PEP 3103 - chb
28
在那个PEP中,Guido没有提到if/elif链也是一个经典的错误来源。这种结构非常脆弱。 - itsbruce
15
所有这里提供的解决方案都没有检测重复案例值。作为一种快速失败原则,这可能比性能或fallthrough功能更重要的遗漏。 - Bob Stein
6
“switch”语句其实比基于输入索引值返回不同固定值的语句更加“通用”。它允许执行不同的代码块,甚至不需要返回值。我想知道这里的某些答案是否是“switch”语句的良好替代品,还是只适用于返回值而没有执行一般代码块的情况。 - sancho.s ReinstateMonicaCellio
3
类似于 Ruby 的 case...when...(或 Scala 的 match、Haskell 的 case、Perl 的 given/when)这样的语法满足了常见用例并提供了强大的抽象。if...elif... 是一个较差的替代品。 - itsbruce
显示剩余2条评论
44个回答

31

我只是想在这里提出一些建议。Python没有case/switch语句的原因是Python遵循“有且仅有一种正确的做法”的原则。显然,你可以想出各种重新创建switch/case功能的方式,但完成这个任务的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 中规定的原则,包括“只有一种正确的做法”。


13
Python为什么有for循环和while循环呢?你可以用while循环实现for循环的所有功能。 - itsbruce
3
真的。switch/case 语句经常被初学者滥用。他们真正想要的是“策略模式”。 - user228395
听起来像是Python希望自己是Clojure。 - T.W.R. Cole
1
@T.W.R.Cole 我不这么认为,Python是最早开始做这个的。Python自1990年以来就存在了,而Clojure则是在2007年才出现的。 - Taylor
2
不支持 fallthru。 - simpleuser
显示剩余3条评论

30

假设你不仅想返回一个值,而是想使用能够改变对象的方法。按照这里提出的方法:

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' 然后评估返回的函数调用。


24

如果你有一个复杂的case块,可以考虑使用函数字典查找表...

如果你以前没有这样做过,最好进入调试器并查看字典如何查找每个函数。

注意: 不要在case /字典查找中使用“()”,否则它会在创建字典/ 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()

我喜欢你的解决方案。但是,如果我只需要传递一些变量或对象怎么办? - Tedo Vrbanec
如果该方法需要参数,则此方法将无法正常工作。 - Kulasangar
1
这是Python FAQ中官方推荐的方法。 - Cestarian
2
它可以使用参数工作,请查看 https://dev59.com/HJbfa4cB1Zd3GeqPox3Y#47378377 - Greg7000

21
如果你正在寻找类似于"switch"的额外语句,我建立了一个Python模块来扩展Python,名为ESPY,即"Enhanced Structure for 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")

因此,将其在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

非常酷,但是在生成的Python代码顶部使用while True:有什么意义呢?它最终会到达生成的Python代码底部的break,因此我认为while True:break都可以被删除。此外,如果用户在自己的代码中使用相同的名称,ESPY是否足够聪明以更改cont的名称?无论如何,我想使用原始的Python,所以我不会使用这个,但它仍然很酷。对于纯粹的酷度,加1。 - ArtOfWarfare
@ArtOfWarfare while True:break 的原因是允许但不要求穿透。 - Solomon Ucko
这个模块还在吗? - Solomon Ucko

19
大多数答案都比较老,特别是被接受的答案,因此更新一下似乎是值得的。
首先,官方的Python FAQ提供了解决方案,并建议在简单情况下使用elif链,在更大或更复杂的情况下使用dict。它还针对某些情况建议使用一组visit_方法(许多服务器框架使用的样式)。
def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

常见问题解答中也提到了PEP 275,它是为了让官方做出一次性决定,是否添加类似C语言的switch语句。但该PEP实际上被推迟至Python 3,并且只有在作为单独提案的PEP 3103中被正式拒绝。答案当然是否定的,但这两个PEP都链接到其他信息,如果您对原因或历史感兴趣。


有一件事情被多次提及(可以在PEP 275中看到,尽管它被削减为实际建议),那就是如果你真的很烦恼于处理4种情况需要8行代码,而在C或Bash中只需要6行,你总是可以写成这样:

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风格的case语句问题,甚至是Go中稍微更强大的版本都被认为已经死了;每当有人在python-ideas或-dev上提出这个问题时,他们都会被引用旧的决定。然而,完整的ML风格模式匹配的想法每隔几年就会出现一次,特别是自从Swift和Rust等语言采用它以来。问题在于,没有代数数据类型很难从模式匹配中获得太多用处。虽然Guido对这个想法表示同情,但没有人提出一个很好地适合Python的建议。(你可以阅读我的2014年草案作为一个例子。)这可能会随着3.7中的dataclass和一些零散的提议来处理总和类型的更强大的enum,或者各种不同类型的语句局部绑定的提议(如PEP 3150,或者目前正在-ideas上讨论的一组提议)而改变。但到目前为止,还没有。也有偶尔提出Perl 6风格的匹配提议,这基本上是从elif到正则表达式到单分派类型切换的混杂。

18

我发现一种常见的开关结构:

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版本不是语句,而是表达式,其会被计算为一个值。


还有,不要使用...parameter...和p1(x),而是使用parameterp1==parameter - Bob Stein
@BobStein-VisiBone 你好,这里是我在Python会话中运行的一个示例:f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'。当我稍后调用f(2)时,我得到了'c'f(1)'b';以及f(0)'a'。至于p1(x),它表示一个谓词;只要它返回TrueFalse,无论是函数调用还是表达式,都可以。 - leo
@BobStein-VisiBone 是的,你说得对!谢谢 :) 为了让多行表达式起作用,应该放置括号,可以像你建议的那样,也可以像我修改后的示例中那样。 - leo
很好。现在我将删除所有关于括号的评论 - Bob Stein

18

扩展“字典作为开关”思想。 如果您想为开关使用默认值:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'

19
我认为更清晰的方式是在字典上使用带有默认值指定的 .get() 方法。我倾向于将异常留给特殊情况,并且这种做法可以减少三行代码和一级缩进,而且不会变得晦涩难懂。 - Chris B.
10
这是一个特殊情况。根据具体情况,它可能是罕见的,也可能不是,但肯定是规则(从此字典中获取内容)的例外情况(返回'default')。Python程序设计时经常使用异常处理。话虽如此,使用get可能会让代码更简洁。 - Mike Graham

16
我使用的解决方案:
这是此处发布的2个解决方案的组合,相对容易阅读并支持默认值。
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


15

你可以使用一个派发的字典:

#!/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

有时候我会使用这个,但它并不像if/elif/elif/else那样清晰。 - F.Tamy

14

读完被接受的答案后,我感到相当困惑,但这篇文章把所有问题都解决了:

def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")

这段代码类似于:

function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}

查看源代码以了解有关将字典映射到函数的更多信息。


读取哪个答案?不止一个。 - Peter Mortensen
@PeterMortensen - 已接受的答案......修复了它。 - Yster
在你的numbers_to_strings函数中,我可以像case between一些数字那样给出条件吗? - Arie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接