【问题标题】:Replacements for switch statement in Python?Python 中 switch 语句的替代品?
【发布时间】:2022-07-18 05:08:16
【问题描述】:

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

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

【问题讨论】:

  • 相关PEP,由Guido本人撰写:PEP 3103
  • @chb 在那个 PEP 中,Guido 没有提到 if/elif 链也是一个典型的错误来源。这是一个非常脆弱的结构。
  • 这里所有的解决方案都缺少检测重复的大小写值.作为快速失败原则,这可能是比性能或 fallthrough 功能更重要的损失。
  • 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
  • 如果性能有问题,我建议将 dict 放在函数之外,这样它就不会在每次函数调用时都重新构建 dict
  • @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 时,您都会再次创建 dict,其次,如果您有更复杂的值,您可以获得异常 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' 的时间也一样长。
  • 这不会在所有情况下每次都评估所有函数/lambdas,即使它只返回一个结果吗?
  • @slf 不,当控制流到达那段代码时,它将构建 3 个函数(通过使用 3 个 lambda 表达式),然后用这 3 个函数作为值构建一个字典,但它们仍然未被调用(评估起初在那种情况下有点模棱两可)。然后通过[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 3.10+ 现在有一个 match/case 语法,类似于 switch/case 等等!

PEP 634 -- Structural Pattern Matching

match/case的精选功能

1 - 匹配值:

匹配值类似于另一种语言中的简单switch/case

match something:
    case 1 | 2 | 3:
        # Match 1-3.
    case _:
        # Anything else.
        # 
        # Match will throw an error if this is omitted 
        # and it doesn't match any of the other patterns.

2 - 匹配结构模式:

match something:
    case str() | bytes():  
        # Match a string like object.
    case [str(), int()]:
        # Match a `str` and an `int` sequence 
        # (`list` or a `tuple` but not a `set` or an iterator). 
    case [_, _]:
        # Match a sequence of 2 variables.
        # To prevent a common mistake, sequence patterns don’t match strings.
    case {"bandwidth": 100, "latency": 300}:
        # Match this dict. Extra keys are ignored.

3 - 捕获变量

解析一个对象;将其保存为变量:

match something:
    case [name, count]
        # Match a sequence of any two objects and parse them into the two variables.
    case [x, y, *rest]:
        # Match a sequence of two or more objects, 
        # binding object #3 and on into the rest variable.
    case bytes() | str() as text:
        # Match any string like object and save it to the text variable.

捕获变量在解析可能以多种不同模式之一出现的数据(例如 JSON 或 HTML)时非常有用。

捕获变量是一个特性。但这也意味着您只需要使用带点的常量(例如:COLOR.RED)。否则,常量将被视为捕获变量并被覆盖。

More sample usage:

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

Python <= 3.9

我最喜欢的开关/案例 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 的这种灵活性,以防万一有人需要这样使用。
  • @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 现有语法的解决方案中最具创意和最实用的。
  • 这真的很好。一项建议的改进是向 Switch 类添加一个(公共)value 属性,以便您可以在语句中引用 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')子类我看不到你从间接中获得了什么。
  • 你不会事先知道调用什么方法,也就是说'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 结构。 IE。,

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

    例 4(仅值):

    option = number['type']
    result = {
        'number':    lambda: 10,       # result = 10
        'text':      lambda: 'ten',    # result = 'ten'
        'binary':    lambda: 0b101111, # result = 47
    }.get(option)()
    

    【讨论】:

    • 是的,例如,如果您的变量 option=="case2" 您的 result=foo2()
    • 等等等等。
    • 是的,我明白目的。但我担心的是,如果你只想要 foo2()foo1()foo3()default() 函数也会运行,这意味着事情可能需要很长时间
    • 省略字典中的 () 。使用get(option)()。问题解决了。
    • 很好地使用 () 是一个很好的解决方案,我做了一个要点来测试它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】:

    如果您正在搜索额外语句,如“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
    

    【讨论】:

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

    这里的大多数答案都很旧,尤其是已接受的答案,因此似乎值得更新。

    首先,官方 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 中看到,尽管它作为实际建议被删掉了)是,如果你真的被 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 风格的 case 语句问题,甚至是 Go 中稍微强大的版本,都被认为已经死了;每当有人在 python-ideas 或 -dev 上提出它时,他们都会提到旧的决定。

    然而,完全 ML 风格模式匹配的想法每隔几年就会出现一次,尤其是在 Swift 和 Rust 等语言采用它之后。问题在于,如果没有代数数据类型,就很难从模式匹配中得到很多用处。虽然 Guido 一直支持这个想法,但没有人提出非常适合 Python 的建议。 (您可以阅读 my 2014 strawman 以获取示例。)这可能会随着 3.7 中的 dataclass 和一些零星的建议而改变,以使用更强大的 enum 来处理求和类型,或者针对不同类型的语句本地绑定的各种建议(比如PEP 3150,或者目前正在讨论的一组提案-ideas)。但到目前为止,还没有。

    偶尔也有针对 Perl 6 风格匹配的建议,这基本上是从 elif 到正则表达式再到单分派类型切换的所有内容的大杂烩。

    【讨论】:

      【解决方案16】:

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

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

      【讨论】:

      • 我认为在指定默认值的字典上使用 .get() 会更清楚。我更喜欢在特殊情况下保留 Exceptions,它减少了三行代码和一定程度的缩进而不是晦涩难懂。
      • 这个特殊情况。它可能是也可能不是稀有的情况取决于有用,但这绝对是规则的例外(回到'default')(从这个字典中得到一些东西)。按照设计,Python 程序会随时使用异常。也就是说,使用get 可能会使代码更好一些。
      【解决方案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 版本不是一个语句,而是一个表达式,它的计算结果是一个值。

      【讨论】:

      • 另外代替...参数...和 ​​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】:

        您可以使用分派的字典:

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

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

        【讨论】:

        • fallthrough到底在哪里?
        • 哎呀!那里有失败,但我不再为 Stack Overflow 做贡献了。一点都不喜欢他们。我喜欢其他人的贡献,但不喜欢 Stackoverflow。如果您对 FUNCTIONALITY 使用 fall through,那么您希望在 switch 中的一个 case 语句中全部捕获某些条件(全部捕获),直到您到达 switch 中的 break 语句。
        • 在这里,值“Dog”和“Cat”均落空,并由相同的功能处理,即它们被定义为具有“四条腿”。它是一个 ABSTRACT 等效于失败,不同的值由发生中断的 SAME case 语句处理。
        • @JDGraham 我认为 Jonas 意味着 fallthrough 的另一个方面,当程序员偶尔忘记在代码末尾为 case 编写 break 时就会发生这种情况。但我认为我们不需要这样的“失败”:)
        【解决方案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 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 - 接受的答案......修复了它。
          【解决方案23】:
          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
          

          【讨论】:

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

          我喜欢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: 一样。
          【解决方案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计算为1,如果expression计算为2,则为Was 2.,如果expression计算为其他值,则为Was something.

            【讨论】:

            • 好吧,失败是有效的,但只是去 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("!")),
                    })
                

                我一直试图以一种让我摆脱“lambda:”的方式重新定义 switch,但放弃了。调整定义:

                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-装饰器一起使用

                  @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
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-11-10
                    • 2011-01-19
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多