PEP 653 – 用于模式匹配的精确语义

猫勺猫勺 03-14 179 阅读 0 评论

抽象

PEP 提出了一种模式匹配的语义,该语义尊重 PEP 634 的一般概念, 但更精确,更容易推理,并且应该更快。

除了 PEP 634 中的属性外,对象模型还将扩展两个特殊 (dunder) 属性,以支持模式匹配。 这两个新属性都必须是整数,并且必须是唯一字符串的元组。__match_container____match_class____match_args____match_args__

有了这个 PEP:

  • 模式匹配的语义会更清晰,这样模式就更容易推理了。

  • 将有可能以更有效的方式实现模式匹配。

  • 模式匹配将更适用于复杂的类,因为它允许类对它们匹配的模式进行更多控制。

赋予动机

Python 中的模式匹配,如 PEP 634 中所述,将添加到 Python 3.10 中。 不幸的是,PEP 634 对语义的处理并不像它可能的那样精确, 它也不允许类充分控制它们如何匹配模式。

精确的语义

PEP 634 明确包括一个关于未定义行为的部分。 在像 C 这样的语言中,大量未定义的行为可能是可以接受的, 但在 Python 中,它应该保持在最低限度。 Python 中的模式匹配可以更精确地定义,而不会损失表现力或性能

改进了对类匹配的控制

PEP 634 委托决定类是序列还是映射到 。 并非所有可被视为序列的类都注册为 的子类。 这种 PEP 允许它们匹配序列模式,而无需完整的机器。collections.abccollections.abc.Sequencecollections.abc.Sequence

PEP 634 赋予某些内置类特权,使其具有特殊形式的匹配,即“自我”匹配。 例如,该模式匹配一个列表,并将该列表分配给 。 通过允许类选择它们匹配的模式类型,其他类也可以使用这种形式。list(x)x

例如,使用 ,我们可能想写成:sympy

# a*a == a**2case Mul(args=[Symbol(a), Symbol(b)]) if a == b:
    return Pow(a, 2)

这要求 sympy 类“自我”匹配。 因为使用 PEP 634 支持这种模式是可能的,但有点棘手。 有了这个 PEP,它可以很容易地实现 。Symbolsympy

鲁棒性

有了这个 PEP,在模式匹配期间对属性的访问就变得定义明确且具有确定性。 这使得模式匹配在匹配具有隐藏副作用的对象(如对象关系映射器)时不容易出错。 对象将对自己的解构有更多的控制权,这有助于防止在属性访问产生副作用时出现意外后果。

PEP 634 在确定值可以匹配的模式时依赖于该模块,并在必要时隐式导入它。 此 PEP 将消除这些导入中令人惊讶的导入错误和误导性审核事件。collections.abc

高效实施

本 PEP 中提出的语义将允许高效实现,部分原因是具有精确的语义 部分来自使用对象模型。

通过精确的语义,可以推断出哪些代码转换是正确的, 从而有效地应用优化

由于对象模型是 Python 的核心部分,因此实现已经有效地处理特殊属性查找。 查找特殊属性比对抽象基类执行子类测试要快得多。

理由

对象模型和特殊方法是 Python 语言的核心。因此 实现很好地支持了它们。 使用特殊属性进行模式匹配允许以以下方式实现模式匹配 与实现的其余部分很好地集成,因此更易于维护,并且可能表现得更好。

match 语句执行一系列模式匹配。一般来说,匹配模式有三个部分:

  1. 该值能否匹配这种模式?

  2. 解构时,该值是否与此特定模式匹配?

  3. 守卫是真的吗?

为了确定一个值是否可以与特定类型的模式匹配,我们添加了 and 属性。 这允许以有效的方式确定值的种类。__match_container____match_class__

规范

对象模型的新增功能

和 属性将添加到 中。 应由想要匹配映射或序列模式的类重写。 应由希望在匹配类模式时更改默认行为的类重写。__match_container____match_class__object__match_container____match_class__

__match_container__必须是整数,并且应该恰好是以下值之一:

0MATCH_SEQUENCE = 1MATCH_mapPING = 2

MATCH_SEQUENCE用于指示类的实例可以匹配序列模式。

MATCH_MAPPING用于指示类的实例可以匹配映射模式。

__match_class__必须是整数,并且应该恰好是以下值之一:

0MATCH_SELF = 8

MATCH_SELF用于指示对于单个位置论证类模式,将使用主题而不是解构。

注意

在本文档的其余部分,我们将仅按名称引用上述值。 将为 Python 和 C 提供符号常量,并且值将 永不改变。

object对于特殊属性,将具有以下值:

__match_container__ = 0__match_class__= 0__match_args__ = ()

这些特殊属性将正常继承。

如果被覆盖,则需要保存唯一字符串的元组。它可能是空的。__match_args__

注意

__match_args__将为 PEP 634 中指定的数据类和命名元组自动生成。

模式匹配实现不需要检查这些属性中的任何行为是否按指定方式运行。 如果 的值 或 未指定,则 实现可能会引发任何异常,或匹配错误的模式。 当然,实现可以自由地检查这些属性,如果它们可以有效地这样做,则可以提供有意义的错误消息。__match_container____match_class____match_args__

匹配过程的语义

在下文中,该表单的所有变量都是临时变量,对 Python 程序不可见。 它们可能通过内省可见,但这是一个实现细节,不应依赖。 伪语句用于表示此模式的匹配失败,并且匹配应移动到下一个模式。 如果控制到达翻译的末尾而没有到达 ,则它已匹配,并忽略以下模式。$varFAILFAIL

形式的变量是包含句法元素的元变量,它们不是正态变量。 所以,不是赋值给, 而是解开了成立的变量。 例如,使用抽象语法 ,然后具体语法将包含变量, 而不是这些变量的值。$ALL_CAPS$VARS = $items$items$VARS$items$VARScase [$VARS]:case[a, b]:$VARS(a, b)

函数接受一个变量并返回该变量的名称。 例如,如果元变量包含该变量,则 .QUOTE$VARfooQUOTE($VAR) == "foo"

下面列出的原始源代码中不存在的所有其他代码都不会触发行事件,符合 PEP 626。

序言

在匹配任何模式之前,将评估正在匹配的表达式:

match expr:

翻译为:

$value = expr

捕获模式

捕获模式始终匹配,因此无可辩驳的匹配:

case capture_var:

翻译为:

capture_var = $value

通配符模式

通配符模式始终匹配,因此:

case _:

翻译为:

# No code -- Automatically matches

文字模式

文字模式:

case LITERAL:

翻译为:

if $value != LITERAL:
    FAIL

除非文字是 、 或 之一 , 当它转化为:NoneTrueFalse

if $value is not LITERAL:
    FAIL

价值模式

价值模式:

case value.pattern:

翻译为:

if $value != value.pattern:
    FAIL

序列模式

不包括星形图案的图案:

case [$VARS]:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
    FAIL
if len($value) != len($VARS):
    FAIL
$VARS = $value

示例:

包含星形图案的图案:

case [$VARS]

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
    FAIL
if len($value) < len($VARS):
    FAIL
$VARS = $value # Note that $VARS includes a star expression.

示例:

映射模式

不包括双星图案的图案:

case {$KEYWORD_PATTERNS}:

翻译为:

$sentinel = object()
$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
    FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
for $KEYWORD in $KEYWORD_PATTERNS:
    $tmp = $value.get(QUOTE($KEYWORD), $sentinel)
    if $tmp is $sentinel:
        FAIL
    $KEYWORD_PATTERNS[$KEYWORD] = $tmp

示例:

包含双星图案的图案:

case {$KEYWORD_PATTERNS, **$DOUBLE_STARRED_PATTERN}:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
    FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
$tmp = dict($value)
if not $tmp.keys() >= $KEYWORD_PATTERNS.keys():
    FAIL:
for $KEYWORD in $KEYWORD_PATTERNS:
    $KEYWORD_PATTERNS[$KEYWORD] = $tmp.pop(QUOTE($KEYWORD))
$DOUBLE_STARRED_PATTERN = $tmp

示例:

类模式

不带参数的类模式:

case ClsName():

翻译为:

if not isinstance($value, ClsName):
    FAIL

具有单个位置模式的类模式:

case ClsName($VAR):

翻译为:

$kind = type($value).__match_class__
if $kind == MATCH_SELF:
    if not isinstance($value, ClsName):
        FAIL
    $VAR = $value
else:
    As other positional-only class pattern

仅位置类模式:

case ClsName($VARS):

翻译为:

if not isinstance($value, ClsName):
    FAIL
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
    raise TypeError(...)
try:
    for i, $VAR in enumerate($VARS):
        $VAR = getattr($value, $attrs[i])
except AttributeError:
    FAIL

示例:

具有所有关键字模式的类模式:

case ClsName($KEYWORD_PATTERNS):

翻译为:

if not isinstance($value, ClsName):
    FAIL
try:
    for $KEYWORD in $KEYWORD_PATTERNS:
        $tmp = getattr($value, QUOTE($KEYWORD))
        $KEYWORD_PATTERNS[$KEYWORD] = $tmp
except AttributeError:
    FAIL

示例:

具有位置和关键字模式的类模式:

case ClsName($VARS, $KEYWORD_PATTERNS):

翻译为:

if not isinstance($value, ClsName):
    FAIL
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
    raise TypeError(...)
$pos_attrs = $attrs[:len($VARS)]
try:
    for i, $VAR in enumerate($VARS):
        $VAR = getattr($value, $attrs[i])
    for $KEYWORD in $KEYWORD_PATTERNS:
        $name = QUOTE($KEYWORD)
        if $name in pos_attrs:
            raise TypeError(...)
        $KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
except AttributeError:
    FAIL

示例:

嵌套模式

上述规范假定模式不是嵌套的。对于嵌套模式 通过引入临时捕获模式,以递归方式应用上述转换。

例如,模式:

case [int(), str()]:

翻译为:

$kind = type($value).__match_class__
if $kind != MATCH_SEQUENCE:
    FAIL
if len($value) != 2:
    FAIL
$value_0, $value_1 = $value
#Now match on temporary values
if not isinstance($value_0, int):
    FAIL
if not isinstance($value_1, str):
    FAIL

警卫

守卫在翻译的其余部分之后转换为测试:

case pattern if guard:

翻译为:

[translation for pattern]if not guard:
    FAIL

不合格特殊属性

所有类都应确保 的值 ,并遵循规范。 因此,实现可以假定(无需检查)满足以下条件:__match_container____match_class____match_args__

__match_container__ == 0 or __match_container__ == MATCH_SEQUENCE or __match_container__ == MATCH_MAPPING__match_class__ == 0 or __match_class__ == MATCH_SELF

这是一个由唯一字符串组成的元组。__match_args__

标准库中类的特殊属性值

对于核心内置容器类,它们将是:__match_container__

  • list:MATCH_SEQUENCE

  • tuple:MATCH_SEQUENCE

  • dict:MATCH_MAPPING

  • bytearray: 0

  • bytes: 0

  • str: 0

命名元组将设置为 。__match_container__MATCH_SEQUENCE

  • 所有其他为 true 的标准库类都将设置为 。issubclass(cls, collections.abc.Mapping)__match_container__MATCH_MAPPING

  • 所有其他为 true 的标准库类都将设置为 。issubclass(cls, collections.abc.Sequence)__match_container__MATCH_SEQUENCE

对于以下内置类,将设置为:__match_class__MATCH_SELF

  • bool

  • bytearray

  • bytes

  • float

  • frozenset

  • int

  • set

  • str

  • list

  • tuple

  • dict

法律优化

上述语义意味着在实现中需要大量的冗余工作和复制。 但是,可以通过采用语义保留转换来有效地实现上述语义 关于幼稚的实现。

执行匹配时,允许实现 将以下函数和方法视为纯函数和方法:

对于任何类支持:MATCH_SEQUENCE

* ``cls.__len__()``
* ``cls.__getitem__()``

对于任何类支持:MATCH_MAPPING

* ``cls.get()`` (Two argument form only)

允许实现做出以下假设:

  • sinstance(obj, cls)可以自由替换,反之亦然。issubclass(type(obj), cls)

  • isinstance(obj, cls)将始终为任何对返回相同的结果,因此可以省略重复调用。(obj, cls)

  • 读取 中的任何一个 是纯操作,并且可以缓存。__match_container____match_class____match_args__

  • 序列(即任何不为零的类)不会通过迭代、下标或调用 来修改。 因此,这些操作可以自由地相互替换,当应用于不可变序列时,它们是等价的。__match_container__ == MATCH_SEQUENCElen()

  • 映射(即任何不为零的类)不会捕获方法的第二个参数。 因此,该值可以自由重复使用。__match_container__ == MATCH_MAPPINGget()$sentinel

事实上,我们鼓励实现做出这些假设,因为这可能会导致性能明显提高。

安全隐患

没有。

实现

规范中遵循的幼稚实现不会非常有效。 幸运的是,有一些相当简单的转换可用于提高性能。 性能应与 PEP 634 的实现(在撰写本文时)在 3.10 版本发布时相当。 进一步的性能改进可能需要等待 3.11 版本。

可能的优化

以下内容不是规范的一部分, 而是帮助开发人员创建高效实现的指南

将评估拆分为多个通道

由于匹配每个模式的第一步是针对 kind 的检查,因此可以在开始时将所有针对 kind 的检查合并到一个多向分支中 的比赛。然后,可以将案例列表复制到几个“通道”中,每个通道对应一种类型。 然后,从每个车道上删除不匹配的案例是微不足道的。 根据种类的不同,每个车道可以采用不同的优化策略。 请注意,match 子句的正文不需要复制,只需要复制模式即可。

序列模式

就性能而言,这可能是最复杂的优化和最有利可图的。 由于每个图案只能匹配一系列长度,通常只能匹配一个长度, 测试序列可以重写为序列的显式迭代, 尝试仅匹配适用于该序列长度的模式。

例如:

case []:
    Acase [x]:
    Bcase [x, y]:
    Ccase other:
    D

大致可以编译为:

  # Choose lane
  $i = iter($value)
  for $0 in $i:
      break
  else:
      A
      goto done
  for $1 in $i:
      break
  else:
      x = $0
      B
      goto done
  for $2 in $i:
      del $0, $1, $2
      break
  else:
      x = $0
      y = $1
      C
      goto done
  other = $value
  D
done:

映射模式

这里最好的策略可能是根据映射的大小和存在的键来形成决策树。 没有必要反复测试是否存在密钥。 例如:

match obj:
    case {a:x, b:y}:
        W
    case {a:x, c:y}:
        X
    case {a:x, b:_, c:y}:
        Y
    case other:
        Z

如果在检查情况 X 时不存在该键,则无需再次检查 Y 键。"a"

映射通道可以大致实现为:

# Choose lane
if len($value) == 2:
    if "a" in $value:
        if "b" in $value:
            x = $value["a"]
            y = $value["b"]
            goto W
        if "c" in $value:
            x = $value["a"]
            y = $value["c"]
            goto X
elif len($value) == 3:
    if "a" in $value and "b" in $value:
        x = $value["a"]
        y = $value["c"]
        goto Y
other = $value
goto Z

此 PEP 和 PEP 634 之间的差异摘要

对语义的更改可以概括为:

  • 需要是一个字符串元组,而不仅仅是一个序列。 这使得模式匹配更加健壮和可优化,因为可以假设它是不可变的。__match_args____match_args__

  • 选择可以匹配的容器模式类型 uses 而不是 和 。cls.__match_container__issubclass(cls, collections.abc.Mapping)issubclass(cls, collections.abc.Sequence)

  • 如有必要,允许类选择完全退出解构,但将 .__match_class__ = 0

  • 匹配模式时的行为被更精确地定义,但在其他方面保持不变。

语法没有变化。PEP 636 教程中给出的所有示例都应该像现在一样继续工作。

被拒绝的想法

使用实例字典中的属性

此 PEP 的早期版本仅在将类模式与默认值匹配时才使用实例字典中的属性。 其目的是避免捕获绑定方法和其他合成属性。但是,这也意味着属性被忽略了。__match_class__

对于班级:

class C:
    def __init__(self):
        self.a = "a"
    @property
    def p(self):
        ...
    def m(self):
        ...

理想情况下,我们将匹配属性“a”和“p”,但不匹配“m”。 但是,没有通用的方法可以做到这一点,因此此 PEP 现在遵循 PEP 634 的语义。

查找主题而不是模式

此 PEP 的早期版本查找了该主题的类别,并且 不是模式中指定的类。 由于以下几个原因,这被拒绝了:__match_args__

* Using the class specified in the pattern is more amenable to optimization and can offer better performance.* Using the class specified in the pattern has the potential to provide better error reporting is some cases.* Neither approach is perfect, both have odd corner cases. Keeping the status quo minimizes disruption.

将 和 合并为单个值

此 PEP 的早期版本合并为一个值 . 使用单个值在性能方面有一点优势, 但在重写类匹配行为时,可能会导致容器匹配发生意外更改,反之亦然。__match_class____match_container____match_kind__

推迟的想法

此 PEP 的原始版本包括匹配类型和特殊方法,这将允许类完全控制它们的匹配。这很重要 对于像 .MATCH_POSITIONAL__deconstruct__sympy

例如,使用 ,我们可能想写成:sympy

# sin(x)**2 + cos(x)**2 == 1case Add(Pow(sin(a), 2), Pow(cos(b), 2)) if a == b:
    return 1

为了支持当前模式匹配的位置模式是可能的, 但很棘手。有了这些附加功能,它可以很容易地实现。sympy

这个想法将在未来的 3.11 的 PEP 中出现。 但是,在 3.10 开发周期中,进行这样的更改为时已晚。

具有一个单独的值来拒绝所有类匹配

在此 PEP 的早期版本中,有一个不同的值,允许类不匹配任何类 需要解构的模式。但是,一旦引入,这将变得多余,并且 使极其罕见的情况的规范复杂化。__match_class__MATCH_POSITIONAL

代码示例

class Symbol:
    __match_class__ = MATCH_SELF

这:

case [a, b] if a is b:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
    FAIL
if len($value) != 2:
    FAIL
a, b = $value
if not a is b:
    FAIL

这:

case [a, *b, c]:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
    FAIL
if len($value) < 2:
    FAIL
a, *b, c = $value

这:

case {"x": x, "y": y} if x > 2:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
    FAIL
$tmp = $value.get("x", $sentinel)
if $tmp is $sentinel:
    FAIL
x = $tmp
$tmp = $value.get("y", $sentinel)
if $tmp is $sentinel:
    FAIL
y = $tmp
if not x > 2:
    FAIL

这:

case {"x": x, "y": y, **z}:

翻译为:

$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
    FAIL
$tmp = dict($value)
if not $tmp.keys() >= {"x", "y"}:
    FAIL
x = $tmp.pop("x")
y = $tmp.pop("y")
z = $tmp

这:

match ClsName(x, y):

翻译为:

if not isinstance($value, ClsName):
    FAIL
$attrs = ClsName.__match_args__
if len($attr) < 2:
    FAIL
try:
    x = getattr($value, $attrs[0])
    y = getattr($value, $attrs[1])
except AttributeError:
    FAIL

这:

match ClsName(a=x, b=y):

翻译为:

if not isinstance($value, ClsName):
    FAIL
try:
    x = $value.a
    y = $value.b
except AttributeError:
    FAIL

这:

match ClsName(x, a=y):

翻译为:

if not isinstance($value, ClsName):
    FAIL
$attrs = ClsName.__match_args__
if len($attr) < 1:
    raise TypeError(...)
$positional_names = $attrs[:1]
try:
    x = getattr($value, $attrs[0])
    if "a" in $positional_names:
        raise TypeError(...)
    y = $value.a
except AttributeError:
    FAIL
class Basic:
    __match_class__ = MATCH_POSITIONAL
    def __deconstruct__(self):
        return self._args

版权

本文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/58.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,179人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码