Python中的结构模式匹配

在Python 3.10之前,我们没有任何内置的方法来使用结构模式匹配,在其他编程语言中被称为switch-case 。从 Python 3.10 发布以来,我们不能使用match ... case 语句来模拟switch ... case 语句。

本教程介绍了结构模式匹配和它在 Python 中的重要性。它还使用不同的模式来演示使用match ... case 语句。

结构模式匹配及其重要性的介绍

在2021年的早期,我们不能在小于或等于3.9的已发布的Python版本中使用match 这个关键字。那时,我们习惯于使用字典或嵌套的if/elif/else 语句来模拟switch ... case

但是,Python 3.10 引入了一个新的功能,称为结构模式匹配 (match ... case 语句)。它相当于我们在Java、C++和其他许多编程语言中的switch ... case 语句。

这个新特性使我们能够编写简单的、易读的、最不容易出错的流程控制语句。

在Python中使用结构模式匹配

结构模式匹配是作为switch ... case 语句使用的,而且比这更强大。如何做到的呢?让我们在下面探讨一些例子,了解它们在不同情况下的用途。

match ... case 语句的基本用法

示例代码:

colour = "blue"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")

OUTPUT:

Blue like sky...

这里,我们首先有一个包含blue 的变量colour 。然后,我们使用match 关键字,它将colour 变量的值与各种指定的情况相匹配,其中每个情况以case 关键字开头,后面是我们要比较或检查的模式。

该模式可以是以下的一种:

  • 字面模式
  • 捕获模式
  • 通配符模式
  • 恒定值模式
  • 序列模式
  • 映射模式
  • 类模式
  • OR模式
  • 海象模式

match ... case 语句只运行第一个匹配的case 下的代码。

如果没有匹配的case ,怎么办?用户怎么会知道呢?为此,我们可以有一个默认的case ,如下所示。

示例代码:

colour = "yellow"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")
    case other:
        print("No match found!")

OUTPUT:

No match found!

使用match ... case 来检测和解构数据结构

示例代码:

student = {
     "name": {"first": "Mehvish", "last": "Ashiq"},
     "section": "B"
}
match student:
    case {"name": {"first": firstname}}:
        print(firstname)

OUTPUT:

Mehvish

在上面的例子中,结构模式匹配在以下两行代码中发挥作用:

match student:
    case {"name": {"first": firstname}}:

我们使用match ... case 语句,通过从student 数据结构中提取学生的名字来查找。这里,student 是一个包含学生信息的字典。

case 行指定了我们的模式来匹配student 。考虑到上面的例子,我们寻找一个带有the "name" 键的字典,其值是一个新的字典。

这个嵌套的字典包含一个"first" key,其值被绑定到firstname 变量。最后,我们使用firstname 变量来打印这个值。

如果你更深入地观察,我们在这里已经学会了映射模式。怎么会呢?映射模式看起来像{"student": s, "emails": [*es]} ,它至少与一组指定的键相匹配的映射。

如果所有的子模式都与它们相应的值相匹配,那么它就把匹配过程中的任何一个子模式与键所对应的值进行绑定。如果我们想允许捕获额外的项目,我们可以在模式的末尾添加**rest

在捕获模式和序列模式中使用match ... case

示例代码:

def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)
sum_list_of_numbers([1,2,3,4])

OUTPUT:

10

在这里,我们使用递归函数来使用捕获模式来捕获与指定模式相匹配的内容,并将其绑定到名称上。

在这个代码例子中,第一个case ,如果它与空列表匹配,则返回0 ,作为一个总和。第二个case 使用带有两个捕获模式的序列模式来匹配具有多个项目/元素之一的列表。

这里,列表中的第一个项目被捕获并绑定到first 名称,而第二个捕获模式*rest ,使用解包语法来匹配任何数量的项目/元素。

请注意,rest 绑定到具有所有数字项目/元素的列表,不包括第一个项目。为了得到输出,我们通过传递上面给出的数字列表来调用sum_list_of_numbers() 函数。

使用match ... case 与通配符模式

示例代码:

def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            incorrect_type = numbers.__class__.__name__
            raise ValueError(f"Incorrect Values. We Can only Add lists of numbers,not {incorrect_type!r}")
sum_list_of_numbers({'1':'2','3':'4'})

OUTPUT:

ValueError: Incorrect Values. We Can only Add lists of numbers, not 'dict'

在学习match ... case 语句的基本用法时,我们已经学习了使用通配符模式的概念,但没有介绍通配符模式术语。想象一下这样的情景:前两种情况都不匹配,我们需要有一个通配符模式作为我们最后的case

例如,如果我们得到任何其他类型的数据结构而不是列表,我们想引发一个错误。在这里,我们可以使用_ 作为通配符模式,它将匹配任何没有绑定到名字的东西。我们在这个最后的case 中添加错误处理,以告知用户。

你怎么说?我们的模式可以使用吗?让我们通过传递一个字符串值的列表来调用sum_list_of_numbers() 函数进行测试,如下所示:

sum_list_of_numbers(['1','2','3','4'])

它将产生以下错误:

TypeError: can only concatenate str (not "int") to str

所以,我们可以说,这个模式还是不够万无一失。为什么呢?因为我们向sum_list_of_numbers() 函数传递了列表类型的数据结构,但有字符串类型的值,而不是我们预期的int类型。

请看下面的章节,了解如何解决这个问题。

使用match ... case 与类模式

示例代码:

def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")
sum_list_of_numbers(['1','2','3','4'])

OUTPUT:

ValueError: Incorrect values! We can only add lists of numbers

基本情况 (第一个case) 返回0 ;因此,求和只对我们可以用数字加的类型有效。注意,Python 不知道如何添加文本字符串和数字。

因此,我们可以使用类模式来限制我们的模式,使之只匹配整数。类模式类似于映射模式,但是匹配的是属性而不是键。

使用match ... case 与OR模式

示例代码:

def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first) | float(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")

假设我们想让sum_list_of_numbers() 函数对一个列表中的值起作用,无论是int型还是float型值。我们使用用管号表示的OR模式 (|)。

如果指定的列表包含int或float类型以外的值,上述代码必须引发ValueError 。让我们考虑以下三种情况进行测试。

测试1:通过一个有int类型值的列表:

sum_list_of_numbers([1,2,3,4]) #output is 10

测试2:通过一个有浮动类型值的列表:

sum_list_of_numbers([1.0,2.0,3.0,4.0]) #output is 10.0

测试3:通过一个有除int和float类型以外的任何其他类型的列表:

sum_list_of_numbers(['1','2','3','4'])
#output is ValueError: Incorrect values! We can only add lists of numbers

你可以看到,由于使用了OR模式,sum_list_of_numbers() 函数对int和float类型的值都有效。

使用match ... case 与文字模式

示例代码:

def say_hello(name):
    match name:
        case "Mehvish":
            print(f"Hi, {name}!")
        case _:
            print("Howdy, stranger!")
say_hello("Mehvish")

OUTPUT:

Hi, Mehvish!

这个例子使用了匹配字面对象的字面模式,例如,一个明确的数字或字符串,正如我们在学习match ... case 语句的基本用法时已经做的那样。

它是最基本的模式类型,让我们模拟类似于Java、C++和其他编程语言的switch ... case 语句。你可以访问这个页面来了解所有的模式。