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

Posted

技术标签:

【中文标题】如何使用存储在变量中的值作为案例模式?【英文标题】:How to use values stored in variables as case patterns? 【发布时间】:2021-05-15 11:18:31 【问题描述】:

我正在尝试理解 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-635 的阅读表明您需要一个值模式,它似乎被定义为一个 dotted 名称。不过,我不确定为什么会出现语法错误,因为 SUCCESS 应该被解释为捕获模式。 “非限定名称(即没有点的裸名称)将始终被解释为捕获模式” 【参考方案1】:

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"

【讨论】:

【参考方案2】:

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 没有第三方库的合格 locals()/globals() 访问

locals()globals() 是 Python 中的内置函数,它们返回一个 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

【讨论】:

【参考方案3】:

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

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

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

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

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

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

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

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)

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

【讨论】:

这个答案让我明白了很多。我不知道 PEP 最终是否可以修改,但我认为关于“为什么这不是 switch/case 语句并且不应该用作一个”的更清晰的一点可以避免很多混乱。至少the "what's new in Python" docs 仍然可以肯定地修改:)【参考方案4】:

除了使用 literal 值之外,PEP 635 的 Value Patterns 部分提到了使用 dotted names 或使用 guards。对比如下:

字面值

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

参考资料:

https://www.python.org/dev/peps/pep-0635/#literal-patterns https://www.python.org/dev/peps/pep-0636/#matching-specific-values

点名

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

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

参考资料:

https://www.python.org/dev/peps/pep-0635/#value-patterns https://www.python.org/dev/peps/pep-0636/#matching-against-constants-and-enums

守卫

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

参考资料:

https://www.python.org/dev/peps/pep-0635/#guards https://www.python.org/dev/peps/pep-0636/#adding-conditions-to-patterns

【讨论】:

【参考方案5】:

如果您正在测试的常量是一个带点的名称,那么它应该被视为一个常量,而不是作为变量的名称来进行捕获(请参阅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 塔可能更安全、更清晰。

【讨论】:

我认为这只是不太清楚,因为还没有正文用于 match 语句。我怀疑该实现可以比if 塔强加的线性搜索更有效地执行模式匹配。 @chepner 给出的规范不能比线性 if 塔快(这就是它目前的实现方式)。由于优先规则,必须按顺序评估案例 您能否详细说明“鉴于 python 如何尝试实现模式匹配”?与 if/else 相比,如何使它变得不那么安全或不那么清晰呢?

以上是关于如何使用存储在变量中的值作为案例模式?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用存储在变量中的值从 python mysql 数据库中获取特定行?

如何将子查询的值存储到 Hana Studio 中的变量中?

我如何使用从表单中获得的值作为我的 dao 类中的变量?

在 Perl 中如何使用变量作为哈希键?

C++ 如何将不同数量的值存储到包含两个变量的结构中的变量?

如何在 C 中分配子数组的值?