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

在其他语言中,我会使用switch或case语句,但Python似乎没有switch语句。在此场景中,推荐的Python解决方案是什么?


当前回答

Python>=3.10

哇,Python 3.10+现在有一个match/case语法,类似于switch/case等等!

PEP 634——结构模式匹配

匹配/案例的选定特征

1-匹配值:

匹配值类似于另一种语言中的简单开关/大小写:

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)。否则,常量将被视为捕获变量并被覆盖。

更多示例用法:

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

我最喜欢的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'))

其他回答

定义:

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()在查找值之前合并字典。它仍然比我想象的更丑,但它的基本效率是对表达式使用散列查找,而不是循环所有键。

我喜欢Mark Bies的回答

由于x变量必须使用两次,我将lambda函数修改为无参数。

我必须运行结果[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类型。因此,这将模拟交换机;其他情况

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

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”然后计算返回的函数调用。

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

首先,官方的Python常见问题解答涵盖了这一点,并为简单案例推荐elif链,为更大或更复杂的案例推荐dict。它还建议在某些情况下使用一组visit_方法(许多服务器框架使用的样式):

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

FAQ还提到了PEP275,它是为了让官方一劳永逸地决定添加C风格切换语句而编写的。但PEP实际上被推迟到了Python 3,它只是作为一个单独的提案PEP3103被正式拒绝。答案当然是否定的,但如果你对原因或历史感兴趣的话,这两位政治公众人物可以获得更多信息。


有一件事多次出现(在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风格的案例陈述,甚至围棋中稍微更强大的版本,都被认为已经过时;每当有人提出python想法或-dev时,他们都会参考旧的决定。

然而,完全ML样式的模式匹配的想法每隔几年就会出现一次,特别是在Swift和Rust等语言采用它之后。问题是,如果没有代数数据类型,很难充分利用模式匹配。虽然圭多一直赞同这个想法,但没有人提出一个非常适合Python的方案。(你可以阅读我2014年的strawman作为一个例子。)这可能会随着3.7中的dataclass和一些零星的建议而改变,比如使用更强大的枚举来处理sum类型,或者使用不同类型的语句本地绑定的各种建议(如PEP3150,或者当前正在讨论的一组建议-ideas)。但到目前为止,它还没有。

偶尔也会有关于Perl 6样式匹配的建议,这基本上是从elif到regex到单分派类型切换的混合。

如果您不担心丢失案例套件中的语法高亮显示,可以执行以下操作:

exec {
    1: """
print ('one')
""", 
    2: """
print ('two')
""", 
    3: """
print ('three')
""",
}.get(value, """
print ('None')
""")

其中value是值。在C中,这将是:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

我们还可以创建一个helper函数来实现这一点:

def switch(value, cases, default):
    exec cases.get(value, default)

因此,我们可以将其用于一、二和三的示例:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")