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

How to use values stored in variables as case patterns?

提问人:jakevdp 提问时间:2/12/2021 最后编辑:Tomerikoojakevdp 更新时间:10/3/2022 访问量:17857

问:

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

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 语句来匹配存储在变量中的值?

python-3.x switch-statement python-3.10 结构模式匹配

评论

0赞 chepner 2/12/2021
我对 PEP-635 的解读表明,您需要一个值模式,它似乎被定义为虚线名称。不过,我不确定为什么您会在这里遇到语法错误,这应该被解释为捕获模式。SUCCESS
3赞 jonrsharpe 2/12/2021
“一个不合格的名称(即一个没有点的裸名称)将始终被解释为捕获模式”

答:

67赞 Green Cloak Guy 2/12/2021 #1

如果要测试的常量是虚线名称,则应将其视为常量,而不是要放入捕获的变量的名称(请参阅 PEP 636 # 与常量和枚举匹配):

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

评论

5赞 chepner 2/12/2021
我认为这不太清楚,因为还没有机构习惯于陈述。我怀疑该实现可以比塔施加的线性搜索更有效地执行模式匹配。matchif
3赞 anthony sottile 2/12/2021
@chepner给定的规格,它不能比线性塔更快(这就是它目前的实现方式)。由于优先规则,必须按顺序评估案例if
5赞 theberzi 2/14/2021
您能详细说明一下“鉴于 python 如何尝试实现模式匹配”吗?如何使它比 if/else 更不安全或更不清晰呢?
24赞 Gary 2/13/2021 #2

除了使用文字值外,PEP 635 的“值模式”部分还提到了虚线名称的使用或防护项的使用。有关比较,请参见下文:

文本值

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

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

引用:

44赞 Brandt Bucher 2/14/2021 #3

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

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

  • 匹配支持的文本,如数字、字符串、布尔值和None
  • 匹配限定(带点)名称
  • 在防护装置中使用其他测试(与模式分开的if)

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

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

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

Python 的模式匹配从来都不是为了支持这样的 C 样式 switch 语句而设计的;这之前已经为Python提出过两次(并被拒绝),所以我们选择了不同的方向。此外,已经有一种明显的方法可以打开单个值,它更简单、更短,并且适用于每个版本的 Python:一个好的 // 梯子!ifelifelse

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)

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

评论

6赞 gerrit 3/3/2021
这个答案让我明白了很多。我不知道 PEP 在最终确定后是否可以修改,但我认为关于“为什么这不是开关/案例声明,不应该用作一个”的更明确的观点可以避免很多混淆。至少“Python 中的新功能”文档仍然可以肯定地进行修改:)
2赞 Linus 12/11/2022
一种选择是具有类似或相似的语法,其中 表示 的数值应用于模式匹配(或者可能表示应将其视为“虚线”变量)。case =SUCCESS:=SUCCESScase .SUCCESSSUCCESS
4赞 nigh_anxiety 1/2/2023
再加上 gerrit 的声明,即使在官方的 Python 文档中,它也说 is for ,而不是 docs.python.org/3/reference/...matchpattern matchingstructural pattern matching
9赞 fameman 4/15/2021 #4

Python 不仅仅是一个简单的 switch 语句。如果你只使用你认为的“变量名称”,它们实际上是捕获模式根据 PEP 第 634 条中的定义match

除了您可能不应该用于您的用例之外,您还必须通过以下方式之一使用限定(虚线)名称: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() 访问

我开发了 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!")

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

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

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

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

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 模块访问

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

constants.py:

SUCCESS = 200
NOT_FOUND = 404

main.py

import constants

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

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

评论

2赞 creanion 7/26/2023
#1 中的示例代码不起作用,因为实例不允许分配自定义属性。object()
-2赞 SuperNova 6/20/2021 #5

Python > 可让您更有效地处理案例模式。3.10

|和语句也可以使用。if

|

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

using 语句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"
0赞 mit 10/3/2022 #6

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

您可以使用不可变数据类型的其他语言执行此操作。

python 中的 match 语句在可能的情况下将值重新绑定到变量,以便它们匹配。

为避免这种情况,请使用文字或虚线结构。有时,使用警卫或声明更容易。if