【问题标题】:Replacements for switch statement in Python?Python中switch语句的替换?
【发布时间】:2021-11-10 06:22:30
【问题描述】:

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

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

【问题讨论】:

  • 相关 PEP,Guido 本人撰写:PEP 3103
  • @chb 在那个 PEP 中,Guido 没有提到 if/elif 链也是典型的错误来源。这是一个非常脆弱的结构。
  • 这里所有解决方案都缺少对重复大小写值的检测。作为一个快速失败的原则,这可能是比性能或故障特性更重要的损失。
  • switch 实际上比基于输入索引值返回不同固定值的东西更“通用”。它允许执行不同的代码。它实际上甚至不需要返回一个值。我想知道这里的一些答案是否可以很好地替代一般的switch 语句,或者仅适用于无法执行一般代码的返回值的情况。
  • 以同样的方式,像 Ruby 的 case...when...(或 Scala 的 match、Haskell 的 case、Perl 的 given/when)这样的语法满足一个常见的用例并提供强大的抽象。 if...elif... 是一个糟糕的替代品。

标签: python switch-statement


【解决方案1】:

下面的原始答案写于 2008 年。从那时起,Python 3.10 (2021) 引入了match-case 语句,它为 Python 提供了一流的“开关”实现。例如:

def f(x):
    match x:
        case 'a':
            return 1
        case 'b':
            return 2
        case _:        
            return 0   # 0 is the default case if x is not found

match-case 语句比这个简单的例子强大得多。


你可以使用字典:

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

【讨论】:

  • 如果找不到 x 会怎样?
  • @nick: 你可以使用 defaultdict
  • 如果性能有问题,我建议将字典放在函数之外,这样它就不会在每个函数调用上重新构建字典
  • @EliBendersky,在这种情况下,使用get 方法可能比使用collections.defaultdict 更正常。
  • @Nick,抛出异常——如果应该有默认值,请使用 }.get(x, default)。 (注意:这比你离开默认的 switch 语句要好得多!)
【解决方案2】:

如果你想要默认值,你可以使用字典 get(key[, default]) 函数:

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 will be returned default if x is not found

【讨论】:

  • 如果'a'和'b'匹配1,'c'和'd'匹配2呢?
  • @JM:好吧,显然字典查找不支持失败。你可以做一个双字典查找。 IE。 'a' & 'b' 指向 answer1 和 'c' 和 'd' 指向 answer2,它们包含在第二个字典中。
  • 这个最好传一个默认值
  • 这种方法有一个问题,首先每次调用 f 你要再次创建字典,如果你有更复杂的值,你可以获得异常 ex。如果 x 是一个元组,我们想做这样的事情 x = ('a') def f(x): return { 'a': x[0], 'b': x[1] }.get( x[0], 9) 这将引发 IndexError
  • @Idan:问题是复制开关。我敢肯定,如果我尝试输入奇数值,我也可以破坏这段代码。是的,它会重新创建,但修复起来很简单。
【解决方案3】:

我一直喜欢这样做

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

From here

【讨论】:

  • 他要求固定值。为什么要在查找时生成一个函数来计算某些东西?其他问题的有趣解决方案。
  • 在这种情况下使用 lambda 可能不是一个好主意,因为每次构建字典时都会调用 lambda。
  • 遗憾的是,这是人们最接近的地方。使用.get()(如当前最高答案)的方法将需要在调度之前急切地评估所有可能性,因此不仅(不仅非常而且)效率极低,而且不会产生副作用;这个答案解决了这个问题,但更冗长。我只会使用 if/elif/else,即使是那些写“case”的时间也一样。
  • 这不会在所有情况下每次都评估所有函数/lambda,即使它只返回一个结果?
  • @slf 不,当控制流到达那段代码时,它会构建 3 个函数(通过使用 3 个 lambda),然后构建一个字典,将这 3 个函数作为值,但是它们首先保持未调用(evaluate 在这种情况下有点模棱两可)。然后字典通过[value] 被索引,这将只返回3 个函数之一(假设value 是3 个键之一)。那时还没有调用该函数。然后(x)x 作为参数调用刚刚返回的函数(结果转到result)。不会调用其他 2 个函数。
【解决方案4】:

除了字典方法(我真的很喜欢,顺便说一句),您还可以使用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

这当然与 switch/case 不同——你不能像离开 break 语句那样容易地失败,但你可以进行更复杂的测试。它的格式比一系列嵌套的ifs 更好,尽管在功能上它更接近。

【讨论】:

  • 我真的更喜欢这个,它使用标准语言结构,如果没有找到匹配的情况,不会抛出 KeyError
  • 我想过字典/get的方式,但标准的方式更易读。
  • @someuser 但他们可以“重叠”的事实是一项功能。您只需确保顺序是应该发生匹配的优先级。至于重复的 x:之前只做一个x = the.other.thing。通常,你会有一个 if、多个 elif 和一个 else,因为这样更容易理解。
  • 很好,不过,“不使用 elif 的失败”有点令人困惑。怎么样:忘记“失败”,只是接受它作为两个if/elif/else's?
  • 另外值得一提的是,当使用x in 'bc' 之类的东西时,请记住"" in "bc"True
【解决方案5】:

Python >= 3.10

哇,Python 现在有了真正的match/case 语法!

PEP 634, '结构模式匹配';又名switch/case,已获得批准,并已添加到 Python 3.10。

Sample usage:

match something:
    case 0 | 1 | 2:
        # Matches 0, 1 or 2
        print("Small number")
    case [] | [_]:
        # Matches an empty or single value sequence
        # Matches lists and tuples but not sets
        print("A short sequence")
    case str() | bytes():
        # Something of `str` or `bytes` type
        print("Something string-like")
    case _:
        # Anything not matched by the above
        print("Something else")

警告:命名常量必须是点名(例如:COLORS.RED),以防止常量被解释为捕获变量。见here


Python

我最喜欢的用于 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;
}

您甚至可以使用元组分配多个变量:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))

【讨论】:

  • 我发现这是一个比公认的更可靠的答案。
  • @some user: C 要求所有情况下的返回值都是相同的类型。 Python 没有。我想强调 Python 的这种灵活性,以防万一有人遇到需要这样使用的情况。
  • @some 用户:我个人觉得 {}.get(,) 是可读的。为了提高 Python 初学者的可读性,您可能需要使用 default = -1; result = choices.get(key, default)
  • 与1行c++比较result=key=='a'?1:key==b?2:-1
  • @Jasen 有人会说你也可以在一行 Python 中做到这一点:result = 1 if key == 'a' else (2 if key == 'b' else 'default')。但是一个班轮可读吗?
【解决方案6】:
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.

【讨论】:

  • 这不是威胁安全的。如果同时按下多个开关,所有开关都取最后一个开关的值。
  • 虽然@francescortiz 可能意味着线程安全,但它也不是威胁安全的。它威胁到变量的值!
  • 线程安全问题可以通过使用thread-local storage 来解决。或者可以通过返回一个实例并使用该实例进行案例比较来完全避免这种情况。
  • @blubberdiblub 但是使用标准的if 语句不是更有效吗?
  • 如果在多个函数中使用,这也是不安全的。在给出的示例中,如果case(2) 块调用了另一个使用switch() 的函数,那么在执行case(2, 3, 5, 7) 等以查找下一个要执行的情况时,它将使用另一个函数设置的开关值而不是那个由当前 switch 语句设置。
【解决方案7】:

我最喜欢的是一个非常好的recipe。这是我见过的最接近实际 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

这是一个例子:

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

一些 cmets 表示使用 with foo as case 而不是 for case in foo 的上下文管理器解决方案可能更干净,对于大型 switch 语句,线性而不是二次行为可能是一个不错的选择。这个带有 for 循环的答案的部分价值是能够有突破和失败,如果我们愿意稍微使用我们选择的关键字,我们也可以在上下文管理器中得到它:

class Switch:
    def __init__(self, value):
        self.value = value
        self._entered = False
        self._broken = False
        self._prev = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        if self._broken:
            return False
        
        if not self._entered:
            if values and self.value not in values:
                return False
            self._entered, self._prev = True, values
            return True
        
        if self._prev is None:
            self._prev = values
            return True
        
        if self._prev != values:
            self._broken = True
            return False
        
        if self._prev == values:
            self._prev = None
            return False
    
    @property
    def default(self):
        return self()

这是一个例子:

# Prints 'bar' then 'baz'.
with Switch(2) as case:
    while case(0):
        print('foo')
    while case(1, 2, 3):
        print('bar')
    while case(4, 5):
        print('baz')
        break
    while case.default:
        print('default')
        break

【讨论】:

  • 我会将for case in switch() 替换为with switch() as case,这样更有意义,因为它只需要运行一次。
  • @Skirmantas:请注意,with 不允许 break,因此取消了 fallthrough 选项。
  • 很抱歉我自己没有付出更多努力来确定这一点:上面的类似答案不是线程安全的。这是吗?
  • @DavidWiniecki 上面缺少的代码组件(可能版权归 activestate)似乎是线程安全的。
  • 这个的另一个版本会像if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"这样吗?
【解决方案8】:
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

【讨论】:

  • 使用上下文管理器是一个很好的创造性解决方案。我建议添加一些解释,也许还可以链接到有关上下文管理器的一些信息,以便为这篇文章提供一些上下文;)
  • 我不太喜欢 if/elif 链,但这是我见过的使用 Python 现有语法的所有解决方案中最有创意和最实用的。
  • 这真是太好了。一项建议的改进是将(公共)value 属性添加到 Switch 类,以便您可以在语句中引用 case.value
  • 这个答案提供了最类似于开关的功能,同时非常简单。使用dict 的问题是您只能检索数据而不能运行函数/方法。
【解决方案9】:

我从 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)

【讨论】:

  • 我看不出这种模式比直接调用方法有什么好处: SMTP().do_HELO('foo.bar.com') 好的,lookupMethod 中可以有通用代码,但因为它也可以被子类覆盖,所以我看不到你从间接中获得了什么。
  • 你不会事先知道调用什么方法,也就是说'HELO'来自一个变量。我在原帖中添加了用法示例
  • 我可以简单地建议:eval('SMTP().do_' + command)('foo.bar.com')
  • 评价?严重地?而不是每次调用都实例化一个方法,我们可以很好地实例化一次并在所有调用中使用它,只要它没有内部状态。
  • IMO 真正的关键是使用 getattr 进行调度以指定要运行的函数。如果方法在模块中,您可以执行 getattr(locals(), func_name) 来获取它。 'do_' 部分有利于安全/错误,因此只能调用带有前缀的函数。 SMTP 本身调用lookupMethod。理想情况下,外界对此一无所知。做 SMTP().lookupMethod(name)(data) 真的没有意义。由于命令和数据在一个字符串中并且 SMTP 会解析它,这更有意义。最后,SMTP 可能还有其他共享状态证明它是一个类。
【解决方案10】:

我只想把我的两分钱花在这里。 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 中规定的原则,包括“做某事只有一种正确的方法。”

【讨论】:

  • 那么为什么Python有for循环和while循环呢?你可以用 for 循环做的所有事情都可以用一个 while 循环来实现。
  • 是的。 Switch/case 经常被初级程序员滥用。他们真正想要的是策略模式
  • 听起来 Python 希望它是 Clojure
  • @T.W.R.Cole 我不这么认为,Python 是第一个这样做的。 Python 于 1990 年问世,Clojure 于 2007 年问世。
  • 不支持fallthru
【解决方案11】:

假设您不想只返回一个值,而是想使用方法来更改对象上的某些内容。使用此处所述的方法是:

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

【讨论】:

    【解决方案12】:

    运行函数的解决方案:

    result = {
        'case1':     foo1, 
        'case2':     foo2,
        'case3':     foo3,
    }.get(option)(parameters_optional)
    

    其中 foo1()、foo2() 和 foo3() 是函数

    示例 1(带参数):

    option = number['type']
    result = {
        'number':     value_of_int,  # result = value_of_int(number['value'])
        'text':       value_of_text, # result = value_of_text(number['value'])
        'binary':     value_of_bin,  # result = value_of_bin(number['value'])
    }.get(option)(value['value'])
    

    示例 2(无参数):

    option = number['type']
    result = {
        'number':     func_for_number, # result = func_for_number()
        'text':       func_for_text,   # result = func_for_text()
        'binary':     func_for_bin,    # result = func_for_bin()
    }.get(option)()
    

    【讨论】:

    • 是的,例如如果你的变量 option=="case2" 你的 result=foo2()
    • 等等等等。
    • 是的,我明白目的。但我担心的是,如果你只想要 foo2()foo1()foo3()default() 函数也会运行,这意味着事情可能需要很长时间
    • 省略字典中的 ()。使用get(option)()。问题解决了。
    • 优秀的使用()是一个grate解决方案,我做了一个gist来测试它gist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
    【解决方案13】:

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

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

    注意:不要在案例/字典查找中使用“()”,否则它会在创建字典/案例块时调用您的每个函数。请记住这一点,因为您只想使用哈希样式查找调用每个函数一次。

    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()
    

    【讨论】:

    • 我喜欢你的解决方案。但是,如果我只需要传递一些变量或对象呢?
    • 如果方法需要参数,这将不起作用。
    • 这是Python FAQ中官方推荐的方法
    • 可以配合参数检查stackoverflow.com/a/47378377/6210975
    【解决方案14】:

    如果您正在搜索额外语句,作为“开关”,我构建了一个扩展 Python 的 Python 模块。它被称为“Python 增强结构”ESPY,它适用于 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
    

    【讨论】:

    • 非常酷,但是生成的 Python 代码顶部的 while True: 有什么意义?它不可避免地会碰到生成的 Python 代码底部的break,所以在我看来while True:break 都可以被删除。此外,如果用户在自己的代码中使用相同的名称,ESPY 是否足够聪明地更改 cont 的名称?无论如何,我想使用 vanilla Python,所以我不会使用它,但它仍然很酷。 +1 纯粹的凉爽。
    • @ArtOfWarfare while True:breaks 的原因是允许但不要求失败。
    • 这个模块还能用吗?
    【解决方案15】:

    扩展“dict as switch”的想法。如果您想为您的开关使用默认值:

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

    【讨论】:

    • 我认为在 dict 上使用 .get() 并指定默认值会更清楚。我更喜欢在特殊情况下保留 Exceptions,它减少了三行代码和一定程度的缩进,而不是晦涩难懂。
    • 一种特殊情况。这可能是也可能不是罕见 情况,具体取决于有用性,但它绝对是规则中的一个例外(回退到'default')(从这个字典中获取一些东西)。按照设计,Python 程序会立即使用异常。话虽如此,使用get 可能会使代码更好一些。
    【解决方案16】:

    这里的大多数答案都已经很老了,尤其是被接受的答案,所以似乎值得更新。

    首先,官方Python FAQ 对此进行了介绍,并推荐elif 链用于简单情况,dict 用于更大或更复杂的情况。它还为某些情况建议了一组visit_ 方法(许多服务器框架使用的样式):

    def dispatch(self, value):
        method_name = 'visit_' + str(value)
        method = getattr(self, method_name)
        method()
    

    FAQ 中还提到了PEP 275,它是为了获得有关添加 C 样式 switch 语句的官方一次性决定而编写的。但是那个 PEP 实际上被推迟到 Python 3,它只是作为一个单独的提案被正式拒绝,PEP 3103。答案当然是否定的,但是如果您对原因或历史感兴趣,这两个 PEP 会提供指向其他信息的链接。


    多次出现的一件事(可以在 PEP 275 中看到,尽管它被作为实际建议删掉了)是,如果您真的对有 8 行代码来处理 4 个案例感到困扰,vs . 你在 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 的建议。 (您可以阅读 my 2014 strawman 的示例。)这可能会随着 3.7 中的 dataclass 和一些零星的提议而改变,即更强大的 enum 来处理 sum 类型,或者针对不同类型的语句本地绑定的各种提议(比如PEP 3150,或者目前正在讨论的一组提案-ideas)。但到目前为止,还没有。

    偶尔也有关于 Perl 6 样式匹配的提议,这基本上是从 elif 到正则表达式再到单调度类型切换的所有内容的混搭。

    【讨论】:

      【解决方案17】:

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

      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 怎么样
      • @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,不管是函数调用还是表达式都可以。
      • @BobStein-VisiBone 是的,你是对的!谢谢 :) 要使多行表达式起作用,应按照您的建议或在我修改后的示例中放置括号。
      • 优秀。现在我将 delete all my comments 讨论括号。
      【解决方案18】:

      我使用的解决方案:

      这里贴的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

      【讨论】:

        【解决方案19】:

        我在 Google 搜索的任何地方都找不到我要寻找的简单答案。但我还是想通了。这真的很简单。决定发布它,也许可以防止在别人的头上少一些划痕。关键是简单的“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.
        

        【讨论】:

        • 到底哪里是fallthrough?
        • 糟糕!那里有跌倒,但我不再为 Stack Overflow 做出贡献。一点也不喜欢他们。我喜欢其他人的贡献,但不喜欢 Stackoverflow。如果您对 FUNCTIONALITY 使用 fall through,那么您希望在 switch 中的一个 case 语句中全部捕获某些条件(catch all),直到您到达 switch 中的 break 语句。
        • 这里“Dog”和“Cat”这两个值都通过并由相同的功能处理,即它们被定义为具有“四条腿”。这是一个 ABSTRACT 等价于 fall through 和不同的值,由发生中断的 SAME case 语句处理。
        • @JDGraham 我认为 Jonas 意味着失败的另一个方面,当程序员偶尔忘记在 case 的代码末尾写 break 时,就会发生这种情况。但我认为我们不需要这样的“失败”:)
        【解决方案20】:

        您可以使用分派的字典:

        #!/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一样不清楚
        【解决方案21】:
        # 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
        

        【讨论】:

          【解决方案22】:
          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
          

          【讨论】:

          • 考虑包含您的代码的简短描述以及它如何解决发布的问题
          • 好的,我现在已经为此添加了评论。
          【解决方案23】:

          我喜欢Mark Bies's answer

          由于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

          【讨论】:

          • None 的情况不是简单地模拟result[None]() 吗?
          • 是的,完全正确。我的意思是result = {'a': 100, None:5000}; result[None]
          • 只是检查没有人认为None: 的行为类似于default:
          【解决方案24】:

          阅读接受的答案后我很困惑,但这一切都清楚了:

          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";
              }
          }
          

          查看Source 了解更多关于字典映射到函数的信息。

          【讨论】:

          • 阅读什么答案?不止一个。
          • @PeterMortensen - 接受的答案......修复了它。
          【解决方案25】:
          def f(x):
               return 1 if x == 'a' else\
                      2 if x in 'bcd' else\
                      0 #default
          

          简短易读,具有默认值并支持条件和返回值中的表达式。

          但是,它的效率低于使用字典的解决方案。例如,Python 在返回默认值之前必须扫描所有条件。

          【讨论】:

            【解决方案26】:

            简单,未经测试;每个条件都是独立评估的:没有失败,但所有情况都被评估(尽管要打开的表达式只评估一次),除非有一个 break 语句。例如,

            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 计算为1Was 2. 如果expression 计算为2Was something. 如果 expression 计算结果为其他值。

            【讨论】:

            • 好吧,失败是可行的,但只能转到 do_default。
            【解决方案27】:

            到目前为止,已经有很多答案说,“我们在 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
            

            现在,您可以使用 switch 语句(如果 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 中切换?”因为这通常是更有趣的问题,并且经常会暴露您正在构建的任何设计中的缺陷。

            现在,这并不是说永远也不应该使用开关。状态机、词法分析器、解析器和自动机都在某种程度上使用它们,一般来说,当您从对称输入开始并转到非对称输出时,它们会很有用;您只需要确保不要将开关用作锤子,因为您会在代码中看到一堆钉子。

            【讨论】:

              【解决方案28】:

              我倾向于使用的也使用字典的解决方案是:

              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)()
              

              这样做的好处是它不会每次都尝试评估函数,您只需确保外部函数获取内部函数所需的所有信息。

              【讨论】:

                【解决方案29】:

                定义:

                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("!")),
                    })
                

                我一直在尝试重新定义 switch,让我摆脱“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() 在查找值之前合并字典。它仍然比我想要的更难看,但它具有在表达式上使用散列查找的基本效率,而不是遍历所有键的循环。

                【讨论】:

                  【解决方案30】:

                  扩展Greg Hewgill's answer - 我们可以使用装饰器封装字典解决方案:

                  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
                  

                  【讨论】:

                    猜你喜欢
                    • 2022-07-18
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-04-12
                    • 1970-01-01
                    相关资源
                    最近更新 更多