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

jak*_*vdp 39 python switch-statement python-3.x python-3.10 structural-pattern-matching

我正在尝试了解Python 3.10 中新的结构模式匹配语法。我知道可以匹配这样的文字值:

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

handle(404)
# not found
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

有没有办法使用 match 语句来匹配存储在变量中的值?

Gre*_*Guy 34

如果您要测试的常量是带点名称的常量,则应将其视为常量而不是将捕获放入的变量的名称(请参阅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')
Run Code Online (Sandbox Code Playgroud)

虽然,考虑到 python 是如何尝试实现模式匹配的,我认为对于这种情况,if/elif/else在检查常量值时只使用一个塔可能更安全、更清晰。

  • 我认为这只是不太清楚,因为还没有主体用于“匹配”语句。我怀疑该实现可以比“if”塔强加的线性搜索更有效地执行模式匹配。 (4认同)
  • @chepner 根据规范,它不可能比线性“if”塔更快(这就是它当前的实现方式)。由于优先规则,必须按顺序评估案件 (4认同)
  • 您能详细说明一下“考虑到 python 如何尝试实现模式匹配”吗?那么,how 会使其比 if/else 更不安全或不那么清晰呢? (3认同)

Bra*_*her 19

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

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

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

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

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

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

Python 的模式匹配从来没有真正设计为支持像这样的 C 风格的 switch 语句;之前曾两次为 Python 提出(但被拒绝),所以我们选择了不同的方向。此外,还有一个已经很明显的方式来开关一个值,这是更简单,更短,并适用于Python中的每一个版本:一个良好的醇” 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)
Run Code Online (Sandbox Code Playgroud)

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

  • 这个答案让我明白了很多。我不知道 PEP 一旦最终确定是否可以修改,但我认为更清楚地说明“为什么这不是 switch/case 语句并且不应该用作一个”可以避免很多混乱。至少[“Python中的新功能”文档](https://docs.python.org/3.10/whatsnew/3.10.html#simple-pattern-match-to-a-literal)仍然可以肯定地进行修改: ) (6认同)
  • 添加到 gerrit 的声明中,即使在官方 Python 文档中,它也说“match”是“模式匹配”,而不是“结构模式匹配”https://docs.python.org/3/reference/compound_stmts.html#the -匹配语句。 (4认同)
  • 一种选择是使用类似于 `case =SUCCESS:` 或类似的语法,其中 `=` 表示应将 `SUCCESS` 的数值用于模式匹配(或者可能使用 `case .SUCCESS` 来指示 ` SUCCESS` 应被视为“点”变量)。 (2认同)

Gar*_*ary 11

除了使用字面值之外,PEP 635的值模式部分还提到了使用虚线名称或使用guards。对比见下图:

字面值

def handle(code):
    match code:
        case 200:
            print('success')
        case 404:
            print('not found')
        case _:
            print('unknown')
Run Code Online (Sandbox Code Playgroud)

参考:

虚线名称

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

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')
Run Code Online (Sandbox Code Playgroud)

参考:

卫兵

[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')
Run Code Online (Sandbox Code Playgroud)

参考:


fam*_*man 9

Pythonmatch不仅仅是一个简单的 switch 语句。如果您只使用您认为的“变量名称”,它们实际上将是捕获模式根据PEP 编号中的定义。第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")
Run Code Online (Sandbox Code Playgroud)

#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")
Run Code Online (Sandbox Code Playgroud)

#3 简单的限定 locals()/globals() 访问

我开发了match-ref 库,它允许您访问任何函数内部或外部的任何本地或全局变量,只需使用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!")
Run Code Online (Sandbox Code Playgroud)

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

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

#4 无需第 3 方库即可进行合格的 locals()/globals() 访问

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

class GetAttributeDict(dict):
    def __getattr__(self, name):
        return self[name]
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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!")
Run Code Online (Sandbox Code Playgroud)

#5 模块访问

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

constants.py:

SUCCESS = 200
NOT_FOUND = 404
Run Code Online (Sandbox Code Playgroud)

main.py

import constants

match retcode:
    case constants.SUCCESS: ...
    ...
Run Code Online (Sandbox Code Playgroud)

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

  • #1 中的示例代码不起作用,因为“object()”实例不允许分配自定义属性。 (2认同)