switch-case
众所周知,Python 中是没有类似 switch-case
结构的语法的。但是自从 3.10 版本发布以后,这种说法就已经成为历史了。
Java 中的 switch
语句类似如下形式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Main {
public static void main(String[] args) {
var option = 3;
switch (option) {
case 1:
System.out.println("You have chosen option 1.");
break;
case 2:
System.out.println("You have chosen option 2.");
break;
case 3:
System.out.println("You have chosen option 3.");
break;
default:
System.out.println("Sorry you chose an invalid option.");
break;
}
}
}
看起来就像是另一种形式的 if-else
语句。以某个变量值作为判断条件,根据不同的判断结果执行对应的语句,最终形成一种流程上的分支结构。
这也许是 Python 不去实现它的依据(借口)之一?都已经有了 if-else
可以足够轻松地完成同样的事情。
There should be one– and preferably only one –obvious way to do it.
字典映射
在模式匹配出现之前,对于分支相当多的判断语句,Python 建议通过字典映射(dictionary mapping)来实现。1
2
3
4
5
6
7
8def function_map(option):
return {
1: lambda : print('You have chose option 1.'),
2: lambda : print('You have chose option 2.'),
3: lambda : print('You have chose option 3.')
}.get(option, lambda: print('Sorry you chose an invalid option.'))
function_map(3)()
借助字典这种数据结构,以匹配条件作为键值,一一对应匹配后需要执行的命令。将 switch
结构中的条件判断转化为对字典键值的搜索匹配。
Pattern Match
用模式匹配实现 switch-case
语法,从形式上看就直观了很多:1
2
3
4
5
6
7
8
9
10
11option = 3
match option:
case 1:
print("You have chosen option 1.")
case 2:
print("You have chosen option 2.")
case 3:
print("You have chosen option 3.")
case _:
print("You chose an invalid option.")
实际上模式匹配不只有创建流程上的分支结构这一种功能,它的作用可以比单纯的 switch-case
语法强大的多。
模式匹配可以算是一种历史悠久的编程技巧了,经常可以在函数式编程语言中见到。比较有代表性的语言比如 Haskell。相对年轻的语言比如 Rust 也引入了功能强大的模式匹配语法。
模式匹配其实可以拆成两部分来理解:匹配和模式。
匹配部分可以发挥类似于 if-else
和 switch
等条件判断语句的作用,生成一种分支结构;模式则定义了特定的规则即匹配的具体条件。更进一步的,还会对匹配到的对象进行解构(destructuring)或者说拆包(unpacking)。
以不同于模式匹配的正则表达式来说:1
2
3
4
5
6
7
8import re
source_str = 'cats are cute'
pattern = re.compile('(.*) are (.*)')
matched = re.match(pattern, source_str)
print(matched.groups())
# => ('cats', 'cute')
正则表达式规则中的 (.*)
分别匹配到源字符串中的 cats
和 cute
,与此同时,还把这两个匹配项提取了出来。
而模式匹配相对来说,则不仅仅能够匹配和提取 cats
、cute
等字符串类型,还能够匹配更复杂类型的对象,同时对匹配到的对象进行拆包操作。
比如下面的代码就对类型为元组的对象进行了匹配和拆包:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def match_person(person):
match person:
case (name, 'M', age):
print(f'He is {name}, aged {age}.')
case (name, 'F', age):
print(f'She is {name}, aged {age}.')
case (name,):
print(f'We only know the name is {name}, others are secrets.')
person_A = ('John', 'M', 20)
person_B = ('Jenny', 'F', 18)
person_C = ('Lily',)
match_person(person_A)
# => He is John, aged 20.
match_person(person_B)
# => She is Jenny, aged 18.
match_person(person_C)
# => We only know the name is Lily, others are secrets.
match
关键字后面被匹配的对象,支持很多种复杂的类型。对应的 case
关键字后面的模式也同样灵活:
- 列表或元组,如
(name, 18)
- 字典,如
{"name": name, "age": 18}
- 使用
*
匹配列表中的剩余部分,如[first, *rest]
- 使用
**
匹配字典中的剩余部分 - 匹配对象和对象的属性
- 在模式中可以使用
|
逻辑或操作
模式匹配应用实例
创建一个 Python 程序,模拟交互式命令行的行为。
匹配字符串
1 | def run_command(command: str) -> None: |
运行效果如下:1
2
3
4
5
6$ reset
Resetting the system.
$ abcdefg
Unknown command: 'abcdefg'.
$ quit
Quitting the program.
匹配列表
1 | def run_command(command: str): |
运行效果:1
2
3
4
5
6
7
8$ load input_data.txt
Loading file: input_data.txt.
$ save output_data.txt
Saving to file: output_data.txt.
$ load input_data.txt output_data.txt
Unkown command: 'load input_data.txt output_data.txt'.
$ bye
Quitting the program.
匹配对象
1 | from dataclasses import dataclass |
运行效果:1
2
3
4
5
6
7
8$ not a command
Unknown command: Command(command='not', arguments=['a', 'command']).
$ load input_data.txt
Loading file: input_data.txt.
$ save output_data.txt
Saving to file: output_data.txt.
$ exit -f
Sending SIGTERM and quitting the program.
需要注意的是,模式匹配中各条 case
语句之间的前后顺序是至关重要的。通常来说,更“具体”更“精确”一些的规则要放在相对靠前的位置。
假如 case Command(command="quit" | "exit" | "bye")
为规则 1,Command(command="quit" | "exit" | "bye", arguments=["--force" | "-f"])
为规则 2。
则更具体一些的规则 2 要放在规则 1 前面。因为模式匹配是从上到下依次检查每一个 case 语句,若遇到匹配的模式,则执行对应的命令。不再继续向下匹配。
由于严格符合规则 2 的对象一定也符合规则 1,当规则 1 位于规则 2 前面时,规则 2 永远也没有被匹配的机会。
可以想象成一种逐渐“滑落”的过程。比如写一个计算成绩等级的函数,可以这样实现:1
2
3
4
5
6
7def grade(score):
if score >= 90:
return 'A'
elif score >= 70:
return 'B'
elif score >= 60:
return 'C'
如果上面 if-else
的条件反着排,那就,所有人都是 C 了。。。
参考资料
A Closer Look At Structural Pattern Matching // New In Python 3.10!