我想用Python编写一个函数,根据输入索引的值返回不同的固定值。
在其他语言中,我会使用switch或case语句,但Python似乎没有switch语句。在此场景中,推荐的Python解决方案是什么?
我想用Python编写一个函数,根据输入索引的值返回不同的固定值。
在其他语言中,我会使用switch或case语句,但Python似乎没有switch语句。在此场景中,推荐的Python解决方案是什么?
当前回答
这里的大多数答案都很陈旧,尤其是那些被接受的答案,因此似乎值得更新。
首先,官方的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到单分派类型切换的混合。
其他回答
虽然已经有了足够的答案,但我想指出一个更简单、更强大的解决方案:
class Switch:
def __init__(self, switches):
self.switches = switches
self.between = len(switches[0]) == 3
def __call__(self, x):
for line in self.switches:
if self.between:
if line[0] <= x < line[1]:
return line[2]
else:
if line[0] == x:
return line[1]
return None
if __name__ == '__main__':
between_table = [
(1, 4, 'between 1 and 4'),
(4, 8, 'between 4 and 8')
]
switch_between = Switch(between_table)
print('Switch Between:')
for i in range(0, 10):
if switch_between(i):
print('{} is {}'.format(i, switch_between(i)))
else:
print('No match for {}'.format(i))
equals_table = [
(1, 'One'),
(2, 'Two'),
(4, 'Four'),
(5, 'Five'),
(7, 'Seven'),
(8, 'Eight')
]
print('Switch Equals:')
switch_equals = Switch(equals_table)
for i in range(0, 10):
if switch_equals(i):
print('{} is {}'.format(i, switch_equals(i)))
else:
print('No match for {}'.format(i))
输出:
Switch Between:
No match for 0
1 is between 1 and 4
2 is between 1 and 4
3 is between 1 and 4
4 is between 4 and 8
5 is between 4 and 8
6 is between 4 and 8
7 is between 4 and 8
No match for 8
No match for 9
Switch Equals:
No match for 0
1 is One
2 is Two
No match for 3
4 is Four
5 is Five
No match for 6
7 is Seven
8 is Eight
No match for 9
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'))
除了字典方法(我很喜欢,BTW),您还可以使用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语句那样轻易地通过,但你可以进行更复杂的测试。它的格式比一系列嵌套的if更好,尽管在功能上更接近。
仅仅将一些键映射到一些代码并不是一个真正的问题,正如大多数人在使用字典时所展示的那样。真正的诀窍是尝试模仿整个直通和中断过程。我认为我从来没有写过一个案例陈述,其中我使用了“功能”。这里有一个直通车。
def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)
case([
(False, lambda:print(5)),
(True, lambda:print(4))
])
我真的把它想象成一个单独的陈述。我希望你能原谅这种愚蠢的格式。
reduce(
initializer=False,
function=(lambda b, f:
( b | f[0]
, { False: (lambda:None)
, True : f[1]
}[b | f[0]]()
)[0]
),
iterable=[
(False, lambda:print(5)),
(True, lambda:print(4))
]
)
我希望这是有效的Python代码。它应该能让你通过。当然,布尔检查可以是表达式,如果您希望它们被延迟求值,那么可以将它们全部封装在lambda中。在执行了列表中的一些项目之后,也不难让它被接受。只需创建元组(bool,bool,function),其中第二个bool指示是否突破或放弃。
假设您不希望只返回一个值,而是希望使用更改对象上某些内容的方法。使用此处所述的方法将是:
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”然后计算返回的函数调用。