ZetCode

Python 模式匹配

最后修改于 2024 年 1 月 29 日

在本文中,我们将展示如何在 Python 中使用模式匹配。

模式匹配使用 match/case 关键字完成。它在 Python 3.10 中引入,名称为结构化模式匹配

模式匹配是一种强大的控制流构造,它允许我们将一个值与一系列模式进行比较,然后根据匹配的模式执行代码。它比 if/else 语句或传统的 switch 语句更高级。

在 if/else 或 switch 语句中,每个单独的条件称为一个分支;在模式匹配中,则使用(arm)一词。

Python 模式匹配字面量

在第一个示例中,我们匹配简单的字面量值。

hello.py
#!/usr/bin/python

langs = ['russian', 'slovak', 'german',
         'swedish', 'hungarian', 'french', 'spanish']

print('say hello')

for lang in langs:
    match lang:
        case 'russian':
            print('привет')
        case 'hungarian':
            print('szia')
        case 'french':
            print('salut')
        case 'spanish':
            print('hola')
        case 'slovak':
            print('ahoj')
        case 'german':
            print('hallo')
        case 'swedish':
            print('Hallå')

我们有一个语言列表。我们遍历列表,为每种语言说“你好”。

for lang in langs:
    match lang:

match 关键字后面跟着要匹配的选项,然后是一个冒号。

case 'russian':
    print('привет')

每个臂都以 case、一个选项和一个冒号开头。

$ ./first.py 
say hello
привет
ahoj
hallo
Hallå
szia
salut
hola

Python 模式匹配多选项

我们可以用 | 为单行提供多个选项。

grades.py
#!/usr/bin/python

grades = ['A', 'B', 'C', 'D', 'E', 'F', 'FX']

for grade in grades:

    match grade:
        case 'A' | 'B' | 'C' | 'D' | 'E' | 'F':
            print('passed')
        case 'FX':
            print('failed')

我们有一个成绩列表。对于 A 到 F 的成绩,我们通过了示例。对于 FX 成绩,我们考试不及格。

$ ./grades.py 
passed
passed
passed
passed
passed
passed
failed

Python 模式匹配通配符

我们可以使用通配符 _ 来表示不匹配任何特定模式的值,或者它也可以用于所有其他模式。

factorial.py
#!/usr/bin/python

def factorial(n):
    match n:
        case 0 | 1:
            return 1
        case _:
            return n * factorial(n - 1)


for i in range(17):
    print(i, factorial(i))

我们使用 match/case 创建了一个阶乘函数。

match n:
    case 0 | 1:
        return 1
    case _:
        return n * factorial(n - 1)

对于值 0 和 1,我们返回 1。对于所有其他值,我们递归调用阶乘函数。

$ ./factorial.py 
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
8 40320
9 362880
10 3628800
11 39916800
12 479001600
13 6227020800
14 87178291200
15 1307674368000
16 20922789888000

Python 模式匹配守卫

if 条件形式的守卫可以在臂上执行。

guards.py
#!/usr/bin/python

import random


n = random.randint(-5, 5)

match n:
    case n if n < 0:
        print(f"{n}: negative value")
    case n if n == 0:
        print(f"{n}: zero")
    case n if n > 0:
        print(f"{n}: positive value")

该示例选择一个随机整数。使用 match/case,我们确定该值是负数、零还是正数。

case n if n < 0:
    print(f"{n}: negative value")

如果 n 小于零,则执行此臂。

Python 模式匹配对象

我们可以对 Python 对象进行模式匹配。

objects.py
from dataclasses import dataclass


@dataclass
class Cat:
    name: str

@dataclass
class Dog:
    name: str

@dataclass
class Person:
    name: str

data = [Cat('Missy'), Dog('Jasper'), Dog('Ace'), Person('Peter'), 'Jupiter']

for e in data:
    match e:
        case Cat(name) | Dog(name):
            print(f'{name} is a pet')
        case Person(name):
            print(f'{name} is a human')
        case _:
            print(f'unknown')

我们有三个类:CatDogPerson。使用 match/case,我们检查我们拥有的是哪种类的对象。

case Cat(name) | Dog(name):
    print(f'{name} is a pet')

此臂检查猫或狗。

case Person(name):
    print(f'{name} is a human')

此臂检查 Person 对象。

case _:
    print(f'unknown')

对于我们无法识别的对象,我们使用通配符。

$ ./objects.py 
Missy is a pet
Jasper is a pet
Ace is a pet
Peter is a human
unknown

在下一个示例中,我们处理 Point 对象。

points.py
#!/usr/bin/python

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


def check(p):
    match p:
        case Point(x=0, y=0):
            print("Origin")
        case Point(x, y) if y == 0:
            print(f"on x axis")
        case Point(x, y) if x == 0:
            print(f"on y axis")
        case Point(x, y) if x > 0 and y > 0:
            print("Q I")
        case Point(x, y) if x < 0 and y > 0:
            print("Q II")
        case Point(x, y) if x < 0 and y < 0:
            print("Q III")
        case Point(x, y) if x > 0 and y < 0:
            print("Q IV")
        case _:
            print("Not a point")


points = [Point(3, 0), Point(0, 0), Point(-4, -5), Point(-4, 0), Point(0, 5),
          Point(4, 8), Point(-5, 3), Point(6, -4)]

for p in points:
    check(p)

根据坐标,我们将点对象分配给原点、x 轴和 y 轴,或四个象限之一。

$ ./points.py 
on x axis
Origin
Q III
on x axis
on y axis
Q I
Q II
Q IV

Python 模式匹配枚举

模式匹配可以有效地与枚举一起使用。

enum.py
#!/usr/bin/python

from enum import Enum
import random


Day = Enum('Day', 'Monday Tuesday Wednesday Thursday Friday Saturday Sunday')


days = [Day.Monday, Day.Tuesday, Day.Wednesday,
        Day.Thursday, Day.Friday, Day.Saturday, Day.Sunday]


res = random.sample(days, 4)

for e in res:

    match e:
        case Day.Monday:
            print("monday")
        case Day.Tuesday:
            print("tuesday")
        case Day.Wednesday:
            print("wednesay")
        case Day.Thursday:
            print("thursday")
        case Day.Friday:
            print("friday")
        case Day.Saturday:
            print("saturday")
        case Day.Sunday:
            print("sunday")

在示例中,我们定义了一个 Day 枚举。

days = [Day.Monday, Day.Tuesday, Day.Wednesday,
    Day.Thursday, Day.Friday, Day.Saturday, Day.Sunday]


res = random.sample(days, 4)

我们从列表中随机选择四天。

for e in res:

    match e:
        case Day.Monday:
            print("monday")
        case Day.Tuesday:
            print("tuesday")
        case Day.Wednesday:
            print("wednesay")
        case Day.Thursday:
            print("thursday")
        case Day.Friday:
            print("friday")
        case Day.Saturday:
            print("saturday")
        case Day.Sunday:
            print("sunday")

我们检查四个选定的值并打印它们对应的字符串表示。

$ ./enums.py 
friday
monday
thursday
tuesday

Python 模式匹配元组

在下面的示例中,我们匹配元组。

tuples.py
#!/usr/bin/python

users = [
    ('John', 'Doe', 'gardener'),
    ('Jane', 'Doe', 'teacher'),
    ('Roger', 'Roe', 'driver'),
    ('Martin', 'Molnar', 'programmer'),
    ('Robert', 'Kovac', 'shopkeeper'),
    ('Tomas', 'Novy', 'programmer'),
]


for user in users:
    match user:
        case (fname, lname, 'programmer'):
            print(f'{fname} {lname} is a programmer')
        case (fname, lname, 'teacher'):
            print(f'{fname} {lname} is a teacher')
        case (fname, lname, 'gardener'):
            print(f'{fname} {lname} is a gardener')
        case _:
            print(user)

我们有一个元组列表。每个元组是一个人及其职业。我们匹配职业。

case (fname, lname, 'programmer'):
    print(f'{fname} {lname} is a programmer')

此臂将人的姓名绑定到 fnamelname 变量,并匹配“programmer”值。

$ ./tuples.py
John Doe is a gardener
Jane Doe is a teacher
('Roger', 'Roe', 'driver')
Martin Molnar is a programmer
('Robert', 'Kovac', 'shopkeeper')
Tomas Novy is a programmer

Python 模式匹配映射

在下一个示例中,我们进行映射的模式匹配。

maps.py
#!/usr/bin/python

users = [
    {'name': 'Paul', 'cols': ['red', 'blue', 'salmon']},
    {'name': 'Martin', 'cols': ['blue']},
    {'name': 'Lucia', 'cols': ['pink', 'brown']},
    {'name': 'Jan', 'cols': ['blue', 'green']},
]

for user in users:
    match user:
        case {'name':name, 'cols': cols}:
            print(f'favourite colours of {name}:')
            for col in cols:
                print(col)

我们有一个由映射表示的用户列表。

case {'name':name, 'cols': cols}:
    print(f'favourite colours of {name}:')
    for col in cols:
        print(col)

case 臂匹配映射并打印每个用户的喜爱的颜色。

$ ./maps.py 
favourite colours of Paul:
red
blue
salmon
favourite colours of Martin:
blue
favourite colours of Lucia:
pink
brown
favourite colours of Jan:
blue
green

来源

PEP 622 – 结构化模式匹配

在本文中,我们研究了 Python 模式匹配。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Python 教程