50

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

4

5 回答 5

43

如果您要测试的常量是一个带点的名称,那么它应该被视为一个常量,而不是作为将捕获放入的变量的名称(参见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在检查常量值时只使用一个塔可能更安全、更清晰。

于 2021-02-11T17:22:09.687 回答
27

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

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

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

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

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

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

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

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)

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

于 2021-02-13T22:50:18.237 回答
15

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

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

参考:

于 2021-02-13T06:34:39.573 回答
6

Pythonmatch不仅仅是一个简单的 switch 语句。如果您只使用您认为的“变量名称”,它们实际上将是Capture Patterns根据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")

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

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

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

locals()并且globals()是 Python 中的内置函数,它们返回dict包含映射到它们各自值的所有变量名的 a。您需要能够使用点分语法访问字典的值,因为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的 s 中),您可能会考虑为此使用单独的模块。

constants.py:

SUCCESS = 200
NOT_FOUND = 404

main.py

import constants

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

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

于 2021-04-15T09:59:58.423 回答
0

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"
于 2021-06-20T04:49:45.010 回答