如何使用存储在变量中的值作为案例模式?

How to use values stored in variables as case patterns?

我正在尝试理解 Python 3.10 中新的 structural pattern matching 语法。我知道可以像这样匹配文字值:

def handle(retcode):
    match retcode:
        case 200:
            print('success')
        case 404:
            print('not found')
        case _:
            print('unknown')

handle(404)
# not found

但是,如果我重构并将这些值移动到模块级变量,则会导致错误,因为语句现在表示结构或模式而不是值:

SUCCESS = 200
NOT_FOUND = 404

def handle(retcode):
    match retcode:
        case SUCCESS:
            print('success')
        case NOT_FOUND:
            print('not found')
        case _:
            print('unknown')

handle(404)
#  File "<ipython-input-2-fa4ae710e263>", line 6
#    case SUCCESS:
#         ^
# SyntaxError: name capture 'SUCCESS' makes remaining patterns unreachable

有什么方法可以使用 match 语句来匹配存储在变量中的值吗?

如果您要测试的常量是一个带点的名称,那么它应该被视为一个常量而不是变量的名称以放入捕获(参见 PEP 636 # Matching against constants and enums):

class Codes:
    SUCCESS = 200
    NOT_FOUND = 404

def handle(retcode):
    match retcode:
        case Codes.SUCCESS:
            print('success')
        case Codes.NOT_FOUND:
            print('not found')
        case _:
            print('unknown')

不过,考虑到 python 如何尝试实现 模式匹配 ,我认为对于这种情况,使用 if/elif/else 检查常数值时的塔。

除了使用 literal 值外,PEP 635 的 Value Patterns 部分还提到使用 dotted names 或使用 守卫 。比较见下:

文字值

def handle(code):
    match code:
        case 200:
            print('success')
        case 404:
            print('not found')
        case _:
            print('unknown')

参考文献:

点名

Any dotted name (i.e. attribute access) is interpreted as a value pattern.

class StatusCodes:
    OK = 200
    NOT_FOUND = 404

def handle(code):
    match code:
        case StatusCodes.OK:
            print('success')
        case StatusCodes.NOT_FOUND:
            print('not found')
        case _:
            print('unknown')

参考文献:

守卫

[A] guard is an arbitrary expression attached to a pattern and that must evaluate to a "truthy" value for the pattern to succeed.

SUCCESS = 200
NOT_FOUND = 404

def handle(code):
    match code:
        case status if status == SUCCESS:
            print('success')
        case status if status == NOT_FOUND:
            print('not found')
        case _:
            print('unknown')

参考文献:

希望我能帮助阐明为什么 裸名在这里如此工作。

首先,正如其他人已经指出的那样,如果您需要匹配值作为模式的一部分,您可以通过以下方式实现:

  • 匹配支持的文字,例如数字、字符串、布尔值和 None
  • 匹配合格(点)名称
  • 在守卫中使用额外的测试(与模式分开if

我担心我们(PEP 作者)可能犯了一个小错误,在早期教程中包含了这个玩具片段……它已经有点病毒式传播了。我们的目标是以最简单的模式匹配示例作为引导,但我们似乎也给许多人造成了令人困惑的第一印象(尤其是在没有上下文的情况下重复时)。

这些 PEP 标题中最容易被忽视的词是“结构”。如果您不匹配主题的 结构结构 模式匹配可能不是完成这项工作的正确工具。

此功能的设计是由解构驱动的(例如在赋值的 LHS 上进行迭代拆包,但对所有 objects 进行了概括),这就是为什么我们使执行核心功能变得非常容易提取 object 的一部分并将它们绑定到名称。我们 认为允许程序员匹配值也是有用的,所以我们添加了那些(条件是在命名值时,它们必须用点限定,为了将它们与更常见的提取物区分开来。

Python 的模式匹配从来没有真正设计用于支持 C-style 这样的 switch 语句;之前曾两次为 Python 提出(并被拒绝),因此我们选择了不同的方向。此外,已经有一个明显的方法来打开单个值,它更简单、更短,并且适用于 Python 的每个版本:a good-ol' if/elif/else天梯!

SUCCESS = 200
NOT_FOUND = 404

def handle(retcode):
    if retcode == SUCCESS:
        print('success')
    elif retcode == NOT_FOUND:
        print('not found')
    else:
        print('unknown')

handle(404)

(如果您真的很关心性能或需要表达式,从字典中调度也是一个不错的选择。)

Python 的 match 不仅仅是一个简单的 switch 语句。如果你只使用你认为的“变量名”,它们实际上将是 Capture Patterns. as per definition in PEP no. 634

除了您可能不应该为您的用例使用 match 之外,您还必须通过以下方式之一使用 qualified(点分)名称:

#1 平面对象

statuses = object()
statuses.success = 200
status.not_found = 404

def handle(retcode):
    match retcode:
        case statuses.success: print("Success")
        case statuses.not_found: print("Not found")

#2 面向对象编程

class StatusValues:
    success = 200
    not_found = 404

def handle(retcode):
    match retcode:
        case StatusValues.success: print("Success")
        case StatusValues.not_found: print("Not found")

#3 简单合格的 locals()/globals() 访问

我开发了 the match-ref library,它允许您访问任何函数内部或外部的任何局部或全局变量,只需使用 ref. 前缀即可。

from matchref import ref
import random

SUCCESS = 200
NOT_FOUND = 404

def handle(retcode):
    random_code = random.randint(600,699)

    match retcode:
        case ref.SUCCESS: print("Success")
        case ref.NOT_FOUND: print("Not found")
        case ref.random_code: print("OK, you win!")

如您所见,ref 自动解析了本地和全局命名空间中的变量(按此顺序)。无需额外设置。

如果你不想使用第 3 方库,你可以在下面看到一个稍微类似的无库版本。

#4 没有第 3 方库的合格 locals()/globals() 访问

locals() and globals() 是 Python 中的内置函数,其中 return 一个 dict 包含映射到它们各自值的所有变量名。您需要能够使用点分语法访问字典的值,因为 match 也不支持字典访问语法。因此,您可以编写这个简单的助手 class:

class GetAttributeDict(dict):
    def __getattr__(self, name):
        return self[name]

并像这样使用它:

import random

SUCCESS = 200
NOT_FOUND = 404

def handle(retcode):
    random_code = random.randint(600, 699)
    globs = GetAttributeDict(globals())
    locs = GetAttributeDict(locals())

    match retcode:
        case globs.SUCCESS: print("Success")
        case globs.NOT_FOUND: print("Not found")
        case locs.random_code: print("OK , you win!")

#5 模块访问

鉴于您似乎打算重新使用您的状态代码(因为否则您可以将它们内联到您的 case 中),您可以考虑为此使用单独的模块。

constants.py:

SUCCESS = 200
NOT_FOUND = 404

main.py

import constants

match retcode:
    case constants.SUCCESS: ...
    ...

同样,您可能需要重新考虑是否要使用 match

python > 3.10 让您更有效地处理大小写模式。

|if 语句也可以使用。

使用|

match name: 
    case "example_111" | "example_222": 
        return f"Hello {name}" 
    case _: 
        return "Bye"

使用 if 语句

def get_product_info(make, in_dollar): 

    match make:

        case "product_111" if in_dollar: 
            return "10000 $"

        case "product_222" if not in_dollar:
            return "10000*73 INR"

        case _: 
            return "error"