【问题标题】:Is there any value to a Switch / Case implementation in Python?Python 中的 Switch / Case 实现有什么价值吗?
【发布时间】:2011-07-23 09:30:57
【问题描述】:

最近,我在网上看到一些关于 Python 中没有好的“switch/case”等价物的讨论。我意识到有几种方法可以做类似的事情——一些使用 lambda,一些使用字典。还有其他关于替代方案的 StackOverflow 讨论。甚至有两个 PEP(PEP 0275 和 PEP 3103)讨论(并拒绝)将 switch/case 集成到语言中。

我想出了一种我认为很优雅的 switch / case 方式。

它最终看起来像这样:

from switch_case import switch, case         # note the import style

x = 42
switch(x)                                    # note the switch statement
if case(1):                                  # note the case statement
    print(1)
if case(2):
    print(2)
if case():                                   # note the case with no args
    print("Some number besides 1 or 2")

所以,我的问题是:这是一个有价值的创作吗?你有什么改进的建议吗?

我放了include file on github,以及大量示例。 (我认为整个包含文件大约有 50 行可执行文件,但我有 1500 行示例和文档。)我是否过度设计了这个东西,浪费了很多时间,还是有人会觉得这值得?

编辑:

试图解释为什么这与其他方法不同:
1)多条路径是可能的(执行两个或多个案例), 这在字典方法中更难。
2)可以检查“等于”以外的比较 (例如 case(less_than(1000))。
3) 比字典方法更具可读性,可能还有 if/elif 方法
4) 可以跟踪有多少真实案例。
5) 可以限制允许的 True 案例的数量。 (即执行 前 2 个真实案例...)
6) 允许默认情况。

这里有一个更详细的例子:

from switch_case import switch, case, between

x=12
switch(x, limit=1)                # only execute the FIRST True case
if case(between(10,100)):         # note the "between" case Function
    print ("%d has two digits."%x)
if case(*range(0,100,2)):         # note that this is an if, not an elif!
    print ("%d is even."%x)       # doesn't get executed for 2 digit numbers,
                                  # because limit is 1; previous case was True.
if case():
    print ("Nothing interesting to say about %d"%x)



# Running this program produces this output:

12 has two digits.

这是一个示例,试图展示 switch_case 如何比传统的 if/else 更清晰和简洁:

# conventional if/elif/else:
if (status_code == 2 or status_code == 4 or (11 <= status_code < 20) 
          or status_code==32):
    [block of code]
elif status_code == 25 or status_code == 45:
    [block of code]
if status_code <= 100:
    [block can get executed in addition to above blocks]

# switch_case alternative (assumes import already)
switch(status_code)
if case (2, 4, between(11,20), 32):   # significantly shorter!
    [block of code]
elif case(25, 45):
    [block of code]
if case(le(100)):
    [block can get executed in addition to above blocks]

大大节省了冗长的 if 语句,其中反复重复相同的 switch。不确定用例的频率有多高,但似乎在某些情况下这是有意义的。

github 上的示例文件还有更多示例。

【问题讨论】:

  • 这应该作为另一个 PEP 提交。
  • 这与传统的if 有何不同?
  • 这个方法很有趣,但是随着编辑,这个问题变成了一个彻头彻尾的广告。您的方法的主要弱点是 switch 语句在词法上没有绑定到您的 case 语句,如果由于某种原因,switch 与其case 块断开连接,这将导致一些非常奇怪的行为。显而易见的解决方案是将switch 做成上下文管理器,这样用户就可以做with switch(x):\n...if case(val):\n......
  • 标准选择/大小写不同是常规 if 的特殊情况。这是将相同的值与各种情况进行多次比较的情况。 select/case 的一个争论的好处是您不需要一遍又一遍地重复相同的“如果 x==”... 的东西。示例:如果 case(1, contains_in(range(10,20)),21,31,41) ... 在传统的 if 中,这将显示为“如果 x==1 或 x 在范围 (10,20) 或 x 中==21 或 x==31,或 x==41"。这个case比较简洁,嗯,case。
  • > 你能做嵌套开关吗? >> 是的,我在 github.com/jerfelix/switch_case/blob/master/… 中的第 578 行到第 697 行的示例中介绍了这一点

标签: python case switch-statement


【解决方案1】:

所以,我的问题是:这是一个有价值的创作吗?

没有。

你有什么改进的建议吗?

是的。不要打扰。它拯救了什么?严重地?实际上,通过从每个 elif 条件中删除变量 x,您已经使代码变得更加变得晦涩难懂了。此外,通过将明显的 elif 替换为 if,您已经为所有人制造了故意的混淆Python 程序员现在会认为案例是独立的。

这会造成混乱。

大大节省了冗长的 if 语句,其中反复重复相同的 switch。不确定用例的频率有多高,但似乎在某些情况下这是有意义的。

没有。这是非常罕见的,非常做作并且非常难以阅读。查看所涉及的实际变量是必不可少的。删除变量名会使事情故意混淆。现在我必须找到拥有的switch() 函数来解释case

当有两个或更多变量时,这完全崩溃了。

【讨论】:

  • 如果是“if”而不是“elif”,则案例是独立的。如果你想使用 elif,你也可以这样做。显然,我的示例是出于演示目的而设计的,但我确信在其他语言(也有 if/then/else 结构)中 select/case 是有价值的。所以感觉在某些情况下 select/case 会有所帮助。我可以想到我用其他语言编写的软件,其中 select/case 绝对是救命稻草。不过感谢反馈。这是一个有趣的练习,只是不确定是否有人可以很好地利用它。
  • “案例是独立的”完全违背了我对“切换”的理解。看看这如何简单地增加了混乱? “我可以想到我用其他语言编写的软件,其中 select/case 绝对是救命稻草。”奇怪的是,我能找到的唯一例子与“if-elif”大致相同。我看不出这个开关是如何澄清任何事情的。
  • 我理解您的反馈并感谢它。我的评论是案例是独立的——我想说“它就像 C”,它可能会失败。如果你想防止跌倒行为,你可以使用 elif 。你的选择。而那个有无数案例、反复对比的软件是80年代的税务计算和财务应用。现在可能不那么常见了。我不会指望你是一个快乐的用户。 :)
  • 我想说的是,C 的失败能力是错误​​和混乱的可怕来源。 标准期望是案例是独立的。这是转换除 C 之外的所有语言的通常期望。C 并不是我们所有人都寻求实现的崇高目标。 C 包含一些我们试图避免的 hack。对 C 开关进行建模会造成混乱。我的回答反映了这种困惑。 “重复比较的大量案例”是我们使用函数和类的目的。我们不需要这个令人困惑的开关。
【解决方案2】:

在 Stackoverflow 上进行了大量讨论来解决这个问题。您可以使用顶部的搜索功能查找其他讨论。

但是,我看不出您的解决方案比基本字典更好:

def switch(x):
    return {
        1 : 1,
        2 : 2,
    }[x]

虽然,使用此方法添加默认子句并非易事。但是,您的示例似乎还是复制了一个复杂的 if/else 语句?不确定我是否会为此包含一个外部库。

【讨论】:

  • 我的新 switch_case.py 库的其他好处:1) 多条路径是可能的(执行两个或多个案例)。 2)可以检查“等于”以外的比较(例如case(less_than(1000)),3)比您描述的字典方法更具可读性。 4)可以跟踪有多少真实案例。 5) 可以限制允许的情况。 6) 允许默认情况。
  • 不要忘记 get() 方法。 return { 1: 1, 2:2 }.get(x, 'default-value')
  • @jerflix:我很抱歉没有阅读您的实施。今晚晚些时候我会再过一遍。看起来确实很有趣。
【解决方案3】:

恕我直言,switch statement 存在的主要原因是它可以被翻译/编译成(非常快)jump table。您提议的实施将如何实现该目标?正如其他海报所展示的那样,Python 的字典今天就可以做到这一点。

其次,我想 switch 语句可能比某些语言中的替代语句读起来更清楚,但在 python 的情况下,我认为 if/elif/else 更清楚。

【讨论】:

  • 到目前为止,这里还没有cmets展示过跳转表的Python字典;他们已经显示了整数的字典查找,但不是可调用的。我同意,如果你追求的是速度,那么跳转表是理想的,但是Python中的字典跳转表对于新手来说并不是很清晰的编码。 (据我了解, dict 的值需要是函数,然后您查找键并调用该值,例如 d[key](args) 变得丑陋,恕我直言。)我试图创建一些东西对新手来说是可读的,而且你不必多次重复你的 switch 变量。
  • @jerfelix,好吧,如果您执行d = {1: myfunc(args), } 之类的操作,则每次查找时都会使用提供的 args 获得 myfunc 的值。这对于有一点编程经验的人来说并不难理解。 d[k](args) 如果您的 dict 和 key 命名得当,那还不错。 ex NEW, UPDATE, CLEAR = range(3); myobjtbl = {NEW: myobj.new, UPDATE: myobj.update, CLEAR: myobj.clear}; myobjtbl[NEW](args); 这还不错。无论语言如何,简短且缺乏描述性名称对于长期可维护性也不是一件好事。他们也不需要ToBeReallyLong)。
  • @GufyMike 没想到,但我不明白它是如何工作的。我在想 d={1:myfunc,} 然后调用 d[1](x) (打开 x)。我试过你的想法,但似乎无法理解。见:pastebin.com/Z4M7urPU你能澄清一下吗?
  • @GufyMike 对不起,我想我在你编辑之前发表了评论,所以我之前的评论可能没有意义。我想我明白你在说什么,但我认为你的语法d = {1: myfunc(args), } 不应该有(arg)。是的,你是对的,有一些方法可以使代码难以使用短名称或长名称来维护。我希望“switch_case”能为可读性提供更多工具。另外,写起来很有趣。 :-) 感谢您的批判眼光。 docs.python.org/faq/… 有像你这样的例子。
  • @jerfleix 我可能歪曲了自己,第一个例子是为了说明它是如何工作的。您的结果是预期的结果。第二个例子是我通常做的。通过正确的命名,外观会有所改善。是的,同意只是写了一个信号调度员....很多,但有时你必须。
【解决方案4】:
from pyswitch import Switch   # pyswitch can be found on PyPI

myswitch = Switch()

@myswitch.case(42)
def case42(value):
    print "I got 42!"

@myswitch.case(range(10))
def caseRange10(value):
    print "I got a number from 0-9, and it was %d!" % value

@myswitch.caseIn('lo')
def caseLo(value):
    print "I got a string with 'lo' in it; it was '%s'" % value

@myswitch.caseRegEx(r'\b([Pp]y\w)\b')
def caseReExPy(matchOb):
    print r"I got a string that matched the regex '\b[Pp]y\w\b', and the match was '%s'" % matchOb.group(1)

@myswitch.default
def caseDefault(value):
    print "Hey, default handler here, with a value of %r." % value

myswitch(5)  # prints: I got a number from 0-9, and it was 5!
myswitch('foobar')  # prints: Hey, default handler here, with a value of foobar.
myswitch('The word is Python')  # prints: I got a string that matched the regex '\b[Pp]y\w\b', and the match was 'Python'

你明白了。为什么?是的,调度表是 Python 的必经之路。我只是厌倦了一遍又一遍地写它们,所以我写了一个类和一些装饰器来为我处理它。

【讨论】:

    【解决方案5】:

    对于类似 switch 的语句,我一直只使用字典、if/elses 或 lambda。通读你的代码 =)

    文档:

    why-isn-t-there-a-switch-or-case-statement-in-python

    【讨论】:

      【解决方案6】:

      2021 年更新:match-case 在 Python 3.10 中引入

      这个激烈争论的话题现在可以结束了。

      事实上,Python 3.10 将在October 2021 中发布,它引入了structural pattern matching,它为语言带来了match-case 结构。

      详情请见this related answer

      【讨论】:

        猜你喜欢
        • 2018-03-23
        • 1970-01-01
        • 2021-06-26
        • 2019-11-18
        • 2022-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多