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

问题描述

我正在尝试了解 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 塔可能更安全、更清晰价值。

,

希望我能帮助阐明为什么裸名称在这里以这种方式工作。

首先,正如其他人已经指出的,如果您需要将值作为模式的一部分进行匹配,您可以这样做:

  • 匹配支持的文字,如数字、字符串、布尔值和 None
  • 匹配限定(虚线)名称
  • 在守卫中使用额外的测试(通过 if 与模式分开)

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

这些 PEP 标题中最容易被忽视的词是“结构性”。如果您不匹配主题的结构结构模式匹配可能不是适合该工作的工具。

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

Python 的模式匹配从来没有真正设计为支持像这样的 C 风格的 switch 语句;之前曾两次为 Python 提出(但被拒绝),所以我们选择了不同的方向。此外,已经有一种明显的方法可以切换单个值,它更简单、更短,并且适用于每个版本的 Python:一个好工具if/elif/{{1} } 梯子!

else

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

,

除了使用 literal 值之外,PEP 635 的 Value Patterns 部分还提到了使用 点名 或使用 guards >.对比见下图:

文字值

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

参考文献:

点名

任何带点的名称(即属性访问)都被解释为值模式。

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] 守卫是附加到模式的任意表达式,必须评估为“真实”值才能使模式成功。

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')

参考文献:

,

Python 的 match 不仅仅是一个简单的 switch 语句。如果您只使用您认为是“变量名”的内容,它们实际上将是 Capture Patterns根据 PEP no. 634

中的定义

除了您可能不应该在用例中使用 match 之外,您还必须通过以下方式之一使用限定(虚线)名称:

#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 自动解析了本地和全局命名空间中的变量(按此顺序)。无需额外设置。

如果你不想使用 3rd-party 库,你可以在下面看到一个稍微相似的无库版本。

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

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

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"