浅析如何在Python中使用结构模式匹配
作者:Serdar Yegulalp
在Python 3.10中引入的模式匹配语法允许在应用程序中使用强大的新编程技术进行决策。Python虽然功能强大且广受欢迎,但长期以来缺乏其他语言中的一种流程控制方式,即以一种优雅的方式将一个值与多个可能的条件进行匹配。在C和C++中,这是通过构造switch/case
语句来实现的;在Rust中,这被称为“模式匹配”。
在Python中,传统的实现方式并不优雅。一种方式是编写一系列的表达式。另一种方式是将要匹配的值作为字典的键存储起来,然后使用这些值来执行相应的操作,例如将函数作为值存储,并使用键或其他变量作为输入。在许多情况下,这种方式可以很好地工作,但构建和维护起来可能会很麻烦,使用if/elif/else
语句。
在经过多次失败的提案后,Python语言创始人Guido van Rossum和其他一些贡献者最近提出的一个提案已被接受,将在Python 3.10中引入结构化模式匹配。结构化模式匹配不仅可以执行简单的样式匹配,还支持更广泛的用例。
Python结构化模式匹配
结构化模式匹配引入了语句和模式语法到Python中。该语句遵循与if/elif/else
相同的基本概述。它接受一个对象,对该对象进行一项或多项匹配模式的测试,并在找到匹配时执行相应的操作。
match command: case "quit": quit() case "reset": reset() case unknown_command: print (f"Unknown command '{unknown_command}'")
每个语句后面都跟着一个要匹配的模式。 在上面的示例中,我们使用简单的字符串作为我们的匹配目标,但更复杂的匹配也是可能的。事实上,结构化模式匹配的主要用例是匹配类型的模式,而不是值的模式。Python通过从上到下遍历案例列表来执行匹配。在第一个匹配时,Python执行相应块中的语句,然后跳到块的末尾并继续执行程序的其余部分。在案例之间没有“穿透”,但可以设计逻辑来处理单个块中的多个可能情况。(稍后会详细介绍)还可以捕获匹配的全部或部分内容并重新使用它。在上面的示例中,如果没有匹配成功,值将被“捕获”在变量中,以便我们可以重新使用它。
用Python结构模式匹配对变量进行匹配
这里有一个需要注意的事项。如果在语句中列出变量名,这并不意味着应该对命名变量的内容进行匹配。在case
语句中,变量用于捕获正在匹配的值。如果想要匹配变量的内容,那么该变量必须以点分隔的名称表示,就像枚举一样。以下是一个示例:
from enum import Enum class Command(Enum): QUIT = 0 RESET = 1 match command: case Command.QUIT: quit() case Command.RESET: reset()
不必使用枚举;任何点分隔的属性名称都可以。但是枚举通常是在Python中执行此操作的最熟悉和惯用的方式。不能通过索引来匹配变量的内容。例如,x[0]
将被拒绝作为语法错误。
使用Python结构化模式匹配进行多个元素的匹配
与模式匹配最有效的工作的关键不仅仅是将其用作字典查找或if/else链的替代品。它是描述要匹配的结构的方式。通过这种方式,可以根据要匹配的元素数量或它们的组合进行匹配。这里是一个稍微复杂一些的例子。在这个例子中,用户输入一个命令,可选择性地跟着一个文件名。
command = input("Command:") match command.split(): case ["quit"]: quit() case ["load", filename]: load_from(filename) case ["save", filename]: save_to(filename) case _: print (f"Command '{command}' not understood")
我们依次来看一下这些情况:
case ["quit"]
:测试我们正在匹配的是否是只包含一个元素的列表,该元素是由输入进行拆分得到的字符串"quit"。case ["load", filename]
:测试第一个拆分元素是否是字符串"load",并且是否有一个紧随其后的字符串。如果是这样,我们将第二个字符串存储在变量filename
中,并将其用于进一步的操作。case ["save", filename]
:与上述情况类似,测试第一个拆分元素是否是字符串"save",并且是否有一个紧随其后的字符串。如果是这样,我们将第二个字符串存储在变量filename
中,并将其用于进一步的操作。case _
:这是一个通配符匹配。如果到目前为止没有进行其他匹配,它将匹配。请注意,下划线变量_
实际上不绑定到任何内容;该名称用作命令的信号,表示该情况是一个通配符。(这就是为什么我们在块的主体中引用该变量;没有捕获到任何内容。)
在Python中结构模式的匹配模式
模式可以是简单的值,也可以包含更复杂的匹配逻辑。以下是一些示例:
case "a"
:匹配单个值"a"。
case ["a","b"]
:匹配集合["a","b"]。
case ["a", value1]
:匹配包含两个值的集合,并将第二个值放入捕获变量value1
中。
case ["a", *values]
:匹配至少包含一个值的集合。其他值(如果有)将存储在values
中。请注意,每个集合中只能包含一个星号项(就像在Python函数中的星号参数一样)。
case ("a"|"b"|"c")
:使用括号运算符()
可以在单个块中处理多个情况。在这里,我们匹配"a"
、"b"
或"c"
。
case ("a"|"b"|"c") as letter
:与上述情况类似,只是现在将匹配的项放入变量letter
中。
case ["a", value] if
:仅当表达式为真时才匹配捕获。可以在表达式中使用捕获变量。例如,如果我们使用value in valid_values
,则只有在捕获的值实际上在集合valid_values
中时,才会匹配。
case ["z", _]
:以"z"开头的任何项集合都会匹配。
使用Python结构化模式匹配对对象进行匹配
Python结构化模式匹配系统最高级的功能是能够针对具有特定属性的对象进行匹配。考虑一个应用程序,我们正在处理一个名为data
的对象,我们希望将其转换为文件并从函数中返回。
match media_object: case Image(codec="jpg"): # Return as-is return media_object case Image(codec="png") | Image(codec="gif"): return render_as(media_object, "jpg") case Video(): raise ValueError("Can't extract frames from video yet") case other_type: raise Exception(f"Media object {media_object} of type {codec} can't be handled yet")
在上述每种情况中,我们都在寻找特定类型的对象,有时还具有特定的属性。第一个情况匹配具有属性image_codec
设置为"jpg"
的对象。第二个情况匹配如果type
是"png"
或"gif"
。第三个情况匹配任何类型为Video
的对象,无论其属性如何。最后一个情况是我们的通配情况,如果其他情况都不匹配,它将捕获所有对象,尽管我们使用了一个实际的名称来捕获它,而不是使用_
。我们还可以在对象匹配中进行捕获:
match media_object: case Image(codec=media_type): print (f"Image of type {media_type}")
有效地使用Python结构模式匹配
Python结构化模式匹配的关键是编写能够覆盖所要匹配的结构情况的匹配。对常量进行简单的测试是可以的,但如果只是这样做,那么简单的字典查找可能是一个更好的选择。结构化模式匹配的真正价值在于能够根据对象的模式进行匹配,而不仅仅是一个特定的对象或一组对象的选择。另一个重要的事情是要记住匹配的顺序。你首先测试哪些匹配将对整体匹配的效率和准确性产生影响。大多数构建了长链的人会意识到这一点,但由于潜在的复杂性,模式匹配要求你更加仔细地思考顺序。将最具体的匹配放在最前面,将最一般的匹配放在最后。最后,如果你的问题可以用简单的链式结构或字典查找来解决,那么请使用它!模式匹配是强大的,但并不是万能的。在解决问题时,请根据问题的特点选择最合适的方法。
到此这篇关于浅析如何在Python中使用结构模式匹配的文章就介绍到这了,更多相关Python结构模式匹配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!