{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python 结构化模式匹配 (PEP 634, 635, 636) - Python 3.10+ 教程\n", "\n", "欢迎来到 Python 3.10+ 中引入的强大特性——结构化模式匹配的教程!这个特性,通常简称为“模式匹配”,提供了一种更富有表现力、更简洁的方式来处理复杂的数据结构,可以替代一系列嵌套的 `if/elif/else` 语句或复杂的字典/列表解析。\n", "\n", "**核心语法:`match` 和 `case`**\n", "\n", "```python\n", "match subject:\n", " case :\n", " # action_1\n", " case :\n", " # action_2\n", " case if :\n", " # action_3 (if guard is true)\n", " case _: # 通配符,匹配任何未被前面case捕获的内容 (可选)\n", " # default_action\n", "```\n", "\n", "**关键点:**\n", "\n", "1. **主题 (Subject)**:`match` 关键字后面的表达式,即我们要匹配的值。\n", "2. **模式 (Pattern)**:`case` 关键字后面的部分。模式可以很简单(如字面量)也可以很复杂(如包含变量捕获、类型检查、结构解包等)。\n", "3. **匹配过程**:Python 会按顺序尝试将 `subject` 与每个 `case` 的 `pattern` 进行匹配。第一个成功匹配的 `case` 块将被执行,然后 `match` 语句结束(没有像C语言 `switch` 那样的“fall-through”行为)。\n", "4. **守卫 (Guard)**:`if ` 子句,附加在 `case` 模式之后。只有当模式匹配成功 **并且** 守卫条件为真时,该 `case` 块才会执行。\n", "5. **通配符 (`_`)**:一个特殊的模式,可以匹配任何东西,但不会绑定到任何名称。通常用作最后一个 `case` 来处理所有未匹配的情况。\n", "\n", "**本教程将涵盖各种模式类型:**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. 字面量模式 (Literal Patterns)\n", "\n", "匹配精确的字面量值,如数字、字符串、`True`, `False`, `None`。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def http_status_description(status_code):\n", " match status_code:\n", " case 200:\n", " return \"OK\"\n", " case 400:\n", " return \"Bad Request\"\n", " case 404:\n", " return \"Not Found\"\n", " case 500:\n", " return \"Internal Server Error\"\n", " case _:\n", " return \"Unknown status code\"\n", "\n", "print(f\"200: {http_status_description(200)}\")\n", "print(f\"404: {http_status_description(404)}\")\n", "print(f\"301: {http_status_description(301)}\")\n", "\n", "def process_command(command):\n", " match command:\n", " case \"start\":\n", " print(\"Starting process...\")\n", " case \"stop\":\n", " print(\"Stopping process...\")\n", " case \"restart\":\n", " print(\"Restarting process...\")\n", " case _:\n", " print(f\"Unknown command: {command}\")\n", "\n", "process_command(\"start\")\n", "process_command(\"pause\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. 捕获模式 (Capture Patterns) 和通配符模式 (Wildcard Pattern)\n", "\n", "* **捕获模式 (变量名)**:如果一个模式是一个未加点的标识符(例如 `x`),它会匹配任何值,并将该值绑定到这个名称上。\n", "* **通配符模式 (`_`)**:匹配任何值,但不绑定到任何名称。用于忽略某个部分。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def describe_value(value):\n", " match value:\n", " case 0:\n", " print(\"Value is zero.\")\n", " case x if isinstance(x, int) and x > 0:\n", " print(f\"Value is a positive integer: {x}\") # x 被捕获\n", " case y if isinstance(y, str):\n", " print(f\"Value is a string: '{y}' of length {len(y)}\") # y 被捕获\n", " case _:\n", " print(f\"Value is something else: {value}\") # _ 匹配但不捕获\n", "\n", "describe_value(0)\n", "describe_value(42)\n", "describe_value(\"hello\")\n", "describe_value([1, 2, 3])\n", "\n", "point = (10, 20, 30)\n", "match point:\n", " case (x, y, _): # 捕获 x 和 y,忽略第三个元素\n", " print(f\"2D projection: x={x}, y={y}\")\n", " case _:\n", " print(\"Not a 3-tuple starting point or something else\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**注意:** 捕获模式的名称不能是 `_` 或其他关键字。如果模式是 `some.attribute`,它被视为值模式 (Value Pattern),而不是捕获模式。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. 值模式 (Value Patterns) 或 点号查找 (Dotted Lookups)\n", "\n", "匹配一个具名常量或枚举成员的值。模式看起来像一个点号路径,如 `Color.RED` 或 `math.pi`。\n", "Python 会计算这个表达式的值,并尝试将 `subject` 与该计算结果进行比较。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from enum import Enum\n", "import math\n", "\n", "class Color(Enum):\n", " RED = 1\n", " GREEN = 2\n", " BLUE = 3\n", "\n", "def get_color_meaning(color: Color):\n", " match color:\n", " case Color.RED: # 值模式\n", " return \"Passion, Danger\"\n", " case Color.GREEN:\n", " return \"Nature, Growth\"\n", " case Color.BLUE:\n", " return \"Calm, Sky\"\n", " case _:\n", " return \"Unknown color\"\n", "\n", "print(f\"Meaning of RED: {get_color_meaning(Color.RED)}\")\n", "print(f\"Meaning of BLUE: {get_color_meaning(Color.BLUE)}\")\n", "\n", "MY_CONSTANT = \"special_value\"\n", "\n", "def check_constant(val):\n", " match val:\n", " case MY_CONSTANT: # 必须是点号查找或已定义的常量名\n", " print(f\"'{val}' matches MY_CONSTANT\")\n", " # case my_constant: # 这会是捕获模式,而不是值模式!\n", " # print(f\"'{val}' captured by my_constant (value: {my_constant})\")\n", " case _:\n", " print(f\"'{val}' does not match MY_CONSTANT\")\n", "\n", "check_constant(\"special_value\")\n", "check_constant(\"other_value\")\n", "\n", "class Constants:\n", " PI_APPROX = 3.14\n", "\n", "def check_pi(val):\n", " match val:\n", " case Constants.PI_APPROX:\n", " print(f\"{val} is approximately pi.\")\n", " case math.pi:\n", " print(f\"{val} is math.pi.\") # 也可以匹配模块级常量\n", " case _:\n", " print(f\"{val} is not a known pi value.\")\n", "\n", "check_pi(3.14)\n", "check_pi(math.pi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**重要区分**:\n", "* `case x:` - `x` 是一个捕获变量,它会匹配并绑定 `subject` 的值。\n", "* `case some.name:` 或 `case QUALIFIED.NAME:` - 这是一个值模式,它会获取 `some.name` 的值,并用该值与 `subject` 进行比较。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. 序列模式 (Sequence Patterns)\n", "\n", "匹配序列类型(如 `list`, `tuple`)。可以匹配固定长度、可变长度,并捕获元素。\n", "* `[p1, p2, ..., pN]`:匹配一个长度为 N 的序列,并将每个元素与对应的子模式 `pi` 匹配。\n", "* `[p1, p2, *rest]`:匹配一个长度至少为 2 的序列,前两个元素与 `p1`, `p2` 匹配,剩余部分(一个列表)被捕获到 `rest`。\n", "* `[p1, p2, *_]`:类似上面,但忽略剩余部分。\n", "* `*` 只能出现一次。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def process_list(data_list):\n", " match data_list:\n", " case []:\n", " print(\"Empty list\")\n", " case [item1]:\n", " print(f\"List with one item: {item1}\")\n", " case [\"start\", command, count]:\n", " print(f\"Start command: {command} with count: {count}\")\n", " case [x, y, z]:\n", " print(f\"List of three items: {x}, {y}, {z}\")\n", " case [\"log\", *messages]: # 星号捕获剩余部分为一个列表\n", " print(f\"Log messages: {messages}\")\n", " case [first, second, *_]: # 星号通配符忽略剩余部分\n", " print(f\"List starts with {first}, {second}, and has more elements.\")\n", " case _:\n", " print(f\"Unmatched list structure: {data_list}\")\n", "\n", "process_list([])\n", "process_list([100])\n", "process_list([\"start\", \"process_A\", 5])\n", "process_list([1, 2, 3])\n", "process_list([\"log\", \"Error 1\", \"Warning 2\", \"Info 3\"])\n", "process_list([\"user\", \"Alice\", 30, \"admin\"])\n", "process_list((1,2)) # 也适用于元组,因为元组是序列" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. 映射模式 (Mapping Patterns)\n", "\n", "匹配映射类型(如 `dict`)。可以匹配特定的键,捕获对应的值,并用 `**rest` 捕获剩余的键值对。\n", "* `{\"key1\": p1, \"key2\": p2}`:匹配一个至少包含 \"key1\" 和 \"key2\" 的字典,它们的值分别与 `p1` 和 `p2` 匹配。\n", "* `{\"key1\": val1, \"key2\": val2, **rest}`:`val1` 和 `val2` 是捕获变量,`rest` 捕获剩余的键值对(一个字典)。\n", "* `**_` 可以用来表示字典可以有其他键,但我们不关心它们。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def process_dict(data_dict):\n", " match data_dict:\n", " case {\"name\": str(name), \"age\": int(age)} if age >= 18:\n", " print(f\"Adult: {name}, Age: {age}\")\n", " case {\"name\": str(name), \"age\": int(age)}:\n", " print(f\"Minor: {name}, Age: {age}\")\n", " case {\"city\": city, \"country\": \"USA\", **other_details}:\n", " print(f\"Location in USA: {city}. Other details: {other_details}\")\n", " case {\"error_code\": code, \"message\": msg, **_}: # 忽略其他键\n", " print(f\"Error {code}: {msg}\")\n", " case {}:\n", " print(\"Empty dictionary\")\n", " case _:\n", " print(f\"Unmatched dictionary structure: {data_dict}\")\n", "\n", "process_dict({\"name\": \"Alice\", \"age\": 30})\n", "process_dict({\"name\": \"Bob\", \"age\": 15})\n", "process_dict({\"city\": \"New York\", \"country\": \"USA\", \"zip\": \"10001\"})\n", "process_dict({\"error_code\": 404, \"message\": \"Resource not found\", \"timestamp\": 12345})\n", "process_dict({})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. 类模式 (Class Patterns)\n", "\n", "匹配特定类的实例,并可以解构其属性。\n", "* `ClassName()`:匹配 `ClassName` 的任何实例。\n", "* `ClassName(attr1=p1, attr2=p2)`:匹配 `ClassName` 的实例,并且其 `attr1` 属性与 `p1` 匹配,`attr2` 属性与 `p2` 匹配。\n", "* `ClassName(pos_arg1, pos_arg2, attr1=p1)`:如果类定义了 `__match_args__` 来指定位置参数的顺序,则可以按位置匹配。\n", "\n", "**`__match_args__`**:一个类属性(通常是元组或列表),列出在类模式中可以用作位置参数的实例属性的名称和顺序。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " # __match_args__ 告诉模式匹配如何按位置解释参数\n", " __match_args__ = (\"x\", \"y\") \n", "\n", " def __init__(self, x, y, label=None):\n", " self.x = x\n", " self.y = y\n", " self.label = label\n", "\n", " def __repr__(self):\n", " return f\"Point({self.x}, {self.y}, label='{self.label}')\"\n", "\n", "class Circle:\n", " __match_args__ = (\"center\", \"radius\")\n", " def __init__(self, center: Point, radius: float):\n", " self.center = center\n", " self.radius = radius\n", "\n", " def __repr__(self):\n", " return f\"Circle(center={self.center!r}, radius={self.radius})\"\n", "\n", "def describe_shape(shape):\n", " match shape:\n", " case Point(x=0, y=0, label=lbl): # 匹配关键字参数\n", " print(f\"Origin Point with label: '{lbl}'\")\n", " case Point(0, 0): # 使用 __match_args__ 进行位置匹配 (x=0, y=0)\n", " print(\"Origin Point (no label or label ignored)\")\n", " case Point(x_coord, y_coord): # 位置捕获\n", " print(f\"Point at ({x_coord}, {y_coord})\")\n", " case Circle(Point(cx, cy), r) if r > 0: # 嵌套模式和守卫\n", " print(f\"Circle centered at ({cx}, {cy}) with radius {r}\")\n", " case Circle(center=_, radius=0):\n", " print(f\"Degenerate circle (point) at {shape.center}\")\n", " case Point(): # 匹配任何 Point 实例\n", " print(\"Some other Point instance\")\n", " case _: \n", " print(\"Unknown shape\")\n", "\n", "describe_shape(Point(0, 0, label=\"Origin\"))\n", "describe_shape(Point(0, 0))\n", "describe_shape(Point(10, 20))\n", "describe_shape(Point(5, 8, label=\"P1\"))\n", "describe_shape(Circle(Point(5,5), 10))\n", "describe_shape(Circle(Point(1,1), 0))\n", "describe_shape(\"Not a shape\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. OR 模式 (OR Patterns)\n", "\n", "使用 `|` (管道符号) 连接多个模式。如果 `subject` 匹配 `|` 左边或右边的任何一个模式,则整个 OR 模式匹配成功。\n", "**限制**:OR 模式中的所有子模式必须绑定相同的变量名集合。如果一个子模式绑定了 `x`,那么其他所有子模式也必须绑定 `x` (或者不绑定任何变量)。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def process_media_command(command):\n", " match command:\n", " case \"play\" | \"start\" | \"resume\":\n", " print(\"Playing media...\")\n", " case \"pause\" | \"hold\":\n", " print(\"Pausing media...\")\n", " case \"stop\" | \"end\" | \"quit\":\n", " print(\"Stopping media...\")\n", " case (\"set_volume\", int(level)) | (\"volume\", int(level)) if 0 <= level <= 100:\n", " print(f\"Setting volume to {level}\")\n", " case (\"set_volume\", _) | (\"volume\", _):\n", " print(\"Invalid volume level.\")\n", " case _:\n", " print(f\"Unknown media command: {command}\")\n", "\n", "process_media_command(\"play\")\n", "process_media_command(\"hold\")\n", "process_media_command(\"quit\")\n", "process_media_command((\"set_volume\", 75))\n", "process_media_command((\"volume\", 120))\n", "process_media_command((\"volume\", \"high\")) # 会匹配第二个 volume case" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. AS 模式 (AS Patterns) - 命名子模式\n", "\n", "使用 `pattern as name`。如果 `pattern` 匹配成功,那么匹配到的 `subject` (或其一部分,取决于 `pattern`) 会被绑定到 `name`。\n", "这对于在复杂模式匹配成功后,仍然需要引用匹配到的整个对象或子对象非常有用。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def process_complex_data(data):\n", " match data:\n", " case [x, y] as pair: # 匹配列表 [x, y],并将整个列表绑定到 pair\n", " print(f\"Pair: {pair}, sum: {x + y}\")\n", " case {\"coord\": (int(a), int(b)) as point, \"value\": val}:\n", " print(f\"Data point {point} at ({a},{b}) has value {val}\")\n", " case str() as text if len(text) > 10:\n", " print(f\"Long string: {text[:10]}... (Full: {text})\")\n", " case (first, *_, last) as sequence if len(sequence) >=3:\n", " print(f\"Sequence: {sequence}, first={first}, last={last}\")\n", " case _:\n", " print(f\"Other data: {data}\")\n", "\n", "process_complex_data([10, 20])\n", "process_complex_data({\"coord\": (5, 3), \"value\": 100, \"meta\": \"info\"})\n", "process_complex_data(\"This is a very long string example.\")\n", "process_complex_data((1,2,3,4,5))\n", "process_complex_data(42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9. 守卫 (Guards)\n", "\n", "如前所述,`if ` 子句可以加在 `case` 模式之后。只有当模式匹配并且守卫条件为真时,`case` 块才会执行。\n", "守卫中可以使用模式中捕获的变量。" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def process_number_with_guard(num):\n", " match num:\n", " case int(x) if x > 0 and x % 2 == 0:\n", " print(f\"{x} is a positive even integer.\")\n", " case int(x) if x > 0:\n", " print(f\"{x} is a positive odd integer.\")\n", " case int(x) if x == 0:\n", " print(f\"{x} is zero.\")\n", " case int(x):\n", " print(f\"{x} is a negative integer.\")\n", " case float(y) if y > 0.0:\n", " print(f\"{y} is a positive float.\")\n", " case _:\n", " print(f\"{num} is not a recognized number type or condition.\")\n", "\n", "process_number_with_guard(10)\n", "process_number_with_guard(7)\n", "process_number_with_guard(0)\n", "process_number_with_guard(-5)\n", "process_number_with_guard(3.14)\n", "process_number_with_guard(\"text\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 总结与最佳实践\n", "\n", "结构化模式匹配是 Python 3.10+ 中一个非常强大的工具,它可以使处理复杂数据结构的代码更加清晰、简洁和易于维护。\n", "\n", "**优点:**\n", "* **可读性**:对于分支逻辑复杂的场景,`match/case` 通常比一长串 `if/elif/else` 更易读。\n", "* **解构赋值**:自然地从数据结构中提取值并赋给变量。\n", "* **详尽性检查的潜力**:虽然 Python 的 `match` 本身不像某些静态语言那样强制进行详尽性检查,但它鼓励思考所有可能的情况(尤其是配合 `_` 通配符)。静态分析工具(如 MyPy)未来可能会在这方面提供更多支持。\n", "* **减少样板代码**:例如,在解析 JSON 数据或处理不同类型的事件时。\n", "\n", "**使用建议:**\n", "* **不要滥用**:对于简单的条件分支,传统的 `if/else` 可能仍然更清晰。\n", "* **优先考虑可读性**:选择最能表达你意图的方式。\n", "* **注意模式的顺序**:更具体的模式应该放在更通用的模式之前,因为 `match` 会在第一个匹配成功后停止。\n", "* **利用守卫**:当模式本身不足以表达条件时,守卫非常有用。\n", "* **善用 `__match_args__`**:对于自定义类,定义 `__match_args__` 可以使类模式更简洁易用。\n", "* **结合类型提示**:模式匹配与类型提示可以很好地协同工作,提高代码的健壮性。\n", "\n", "结构化模式匹配为 Python 开发者提供了一个强大的新工具。通过实践和熟悉各种模式类型,你可以有效地利用它来编写更优雅的 Python 代码。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 5 }