PEP 661 – 哨兵值

猫勺猫勺 03-14 1.01 K 阅读

抽象

唯一占位符值,通常称为“哨兵值”,常见于 编程。它们有很多用途,例如:

  • 函数参数的默认值,用于未给出值时:

def foo(value=None):
    ...
  • 当找不到某些内容或不可用时,从函数返回值:

>>> "abc".find("d")-1
  • 缺少数据,例如关系数据库中的 NULL 或“N/A”(“not avAIlable“) 在电子表格中

Python 具有 特殊值 ,旨在用作 在大多数情况下是哨兵值。然而,有时是替代哨兵 值是必需的,通常当它需要与 不同时。这些 案例很常见,以至于实现此类哨兵的几个成语 多年来一直出现,但并不常见,以至于没有 明确需要标准化。但是,常见的实现, 包括 stdlib 中的一些,有几个明显的缺点。NoneNone

PEP 建议添加一个用于定义哨兵值的实用程序,以供使用 在 stdlib 中,并作为 stdlib 的一部分公开提供。

注意:更改 stdlib 中要实现的所有现有哨兵 方式不被认为是必要的,是否这样做由自行决定 的维护者。

赋予动机

2021 年 5 月,python-dev 邮件列表 上提出了一个关于如何更好地实现 .现有实现使用了 以下常见成语:traceback.print_exception

_sentinel = object()

但是,此对象具有信息量不足且过于冗长的 repr,导致 函数的签名过长且难以阅读:

>>> help(traceback.print_exception)Help on function print_exception in module traceback:print_exception(exc, /, value=<object object at0x000002825DF09650>, tb=<object object at 0x000002825DF09650>,limit=None, file=None, chain=True)

此外,还提出了许多现有哨兵的另外两个缺点 在讨论中:

没有明显的类型,因此无法定义清晰 以 Sentinels 为默认值的函数的类型签名

复制或取消腌制后的错误行为,由于单独的 正在创建的实例,因此使用 failure 进行比较is

在随后的讨论中,Victor Stinner 提供了当前使用的清单 Python 标准库  中的 sentinel 值。这表明 对哨兵的需求是相当普遍的,有各种实现 甚至在 stdlib 中使用的方法,其中许多方法都受到 at 上述三个缺点中的至少一个。

讨论没有就标准是否达成任何明确的共识 实现方法是需要的还是可取的,无论是否提到缺点 是重要的,也不是哪种实现是好的。作者 的这个 PEP 在 bugs.python.org  上创建了一个问题,建议 改进,但只关注少数几个有问题的方面 案件,未能获得任何支持。

在 discuss.python.org 上创建了一个民意调查,以更清楚地了解 社区的意见。民意调查的结果不是决定性的,只有40% 投票支持“现状很好/不需要一致性 这“,但大多数选民投票支持一种或多种标准化解决方案。 具体来说,37%的选民选择了“始终如一地使用新的、专用的 Sentinel Factory / Class / Meta-Class,也在 stdlib”。

由于意见不一,这个 PEP 的创建是为了促进做出决定 关于这个主题。

在处理此 PEP 时,迭代各种选项和实现 并不断讨论,笔者得出的结论是,一个简单的, 标准库中可用的良好实现是值得拥有的, 既可用于标准库本身,也可用于其他地方。

理由

指导所选实施的标准是:

  1. 哨兵对象的行为应与哨兵对象预期的行为相同: 当 与使用运算符相比,应始终考虑 与自身相同,但从不与任何其他对象相同。is

  2. 创建哨兵对象应该是一个简单、直接的单行。

  3. 根据需要定义尽可能多的不同哨兵值应该很简单。

  4. 哨兵对象应具有清晰而简短的 repr。

  5. 应该可以对哨兵使用明文类型签名。

  6. 哨兵对象在复制和/或复制后应表现正常 不腌制。

  7. 这样的哨兵在使用 CPython 3.x 和 PyPy3 时应该可以工作,理想情况下 也适用于 Python 的其他实现。

  8. 在实施中,尤其是尽可能简单明了 使用中。避免这成为学习时要学习的一件更特别的事情 蟒。它应该在需要时易于查找和使用,并且足够明显 在阅读代码时,人们通常不会觉得有必要查找其 文档

由于 Python 标准库  中有如此多的用途,因此 在标准库中有一个实现,因为 stdlib 不能使用 其他位置可用的 sentinel 对象的实现(例如  或 PyPI 包)。sentinelssentinel

在研究了现有的习语和实现之后,并经历了许多 不同的可能实现,编写了一个满足 所有这些标准(请参阅参考下面的实现)。

规范

一个新类将添加到新模块中。 它的初始值设定项将接受一个必需的参数,即 Sentinel 对象和两个可选参数:对象的 repr 和 其模块名称:Sentinelsentinels

>>> from sentinels import Sentinel>>> NotGiven = Sentinel('NotGiven')>>> NotGiven<NotGiven>>>> MISSING = Sentinel('MISSING', repr='mymodule.MISSING')>>> MISSINGmymodule.MISSING>>> MEGA = Sentinel('MEGA', repr='<MEGA>', module_name='mymodule')<MEGA>

检查值是否为这样的哨兵应使用运算符完成,建议用于 。使用意志进行平等检查 也按预期工作,仅在比较对象时返回 与它自己。身份检查,例如通常应该 而不是布尔检查,例如 或 。 默认情况下,Sentinel 实例是真实的。isNone==Trueif value is MISSING:if value:if not value:

哨兵的名称在每个模块中都是唯一的。在调用已经具有该名称的哨兵的模块时 定义时,将返回具有该名称的现有哨兵。哨兵 在不同的模块中具有相同的名称将彼此不同。Sentinel()

创建哨兵对象的副本,例如 using 或 酸洗和取消腌制,将返回相同的对象。copy.copy()

哨兵值的类型注释应使用 。例如:Sentinel

def foo(value: int | Sentinel = MISSING) -> int:
    ...

通常不需要提供可选参数, 因为通常能够识别它所在的模块 叫。 只有在特殊情况下才应提供 自动识别无法按预期工作,例如在使用 Jython 或 IronPython。这与 和 的设计相似。有关详细信息,请参阅 PEP 435。module_nameSentinel()module_nameEnumnamedtuple

该类可以被子类化。每个子类的实例将 保持唯一,即使使用相同的名称和模块。这允许 自定义哨兵的行为,例如控制其真实性。Sentinel

参考实现

参考实现可在专用的 Github 存储库  中找到。一个 简化版本如下:

_registry = {}class Sentinel:    """Unique sentinel values."""

    def __new__(cls, name, repr=None, module_name=None):
        name = str(name)
        repr = str(repr) if repr else f'<{name.split(".")[-1]}>'
        if module_name is None:
            try:
                module_name = \                    sys._getframe(1).f_globals.get('__name__', '__main__')
            except (AttributeError, ValueError):
                module_name = __name__

        registry_key = f'{module_name}-{name}'

        sentinel = _registry.get(registry_key, None)
        if sentinel is not None:
            return sentinel

        sentinel = super().__new__(cls)
        sentinel._name = name
        sentinel._repr = repr
        sentinel._module_name = module_name

        return _registry.setdefault(registry_key, sentinel)

    def __repr__(self):
        return self._repr

    def __reduce__(self):
        return (
            self.__class__,
            (
                self._name,
                self._repr,
                self._module_name,
            ),
        )

被拒绝的想法

这存在“基本原理”部分提到的所有缺点。

添加单个新的哨兵值,例如

由于这样的值可以用于不同地方的各种事物,因此一 不能总是确信它在某些用途中永远不会是有效值 例。另一方面,可以使用专用且独特的哨兵值 充满信心,无需考虑潜在的边缘情况。

此外,能够提供有意义的名称和 repr 也很有用 对于特定于使用它的上下文的 Sentinel 值。

最后,这在民意调查中是一个非常不受欢迎的选择,只有12% 投票赞成票的票数。

使用现有的 sentinel 值

这不是省略号的最初预期用途,尽管它已成为 用它来定义空类或功能块越来越普遍 的使用 .pass

此外,与潜在的新单个哨兵值类似,不能是 在所有情况下都自信地使用,这与专用的、独特的价值不同。Ellipsis

使用单值枚举

建议的成语是:

class NotGivenType(Enum):
    NotGiven = 'NotGiven'NotGiven = NotGivenType.NotGiven

除了过度重复之外,repr 也过长:.可以定义一个较短的 repr,在 多一点代码和更多重复的代价。<NotGivenType.NotGiven: 'NotGiven'>

最后,这个选项是九个选项中最不受欢迎的 民意调查 ,是唯一获得无票的选择。

哨兵类装饰器

建议的成语是:

@sentinel(repr='<NotGiven>')class NotGivenType: passNotGiven = NotGivenType()

虽然这允许非常简单明了地实现装饰器, 这个成语太冗长,重复,很难记住。

使用类对象

由于类本质上是单例,因此使用类作为哨兵值 有意义,并允许简单的实现。

最简单的版本是:

class NotGiven: pass

要获得明确的 repr,需要使用元类:

class NotGiven(metaclass=SentinelMeta): pass

...或类装饰器:

@Sentinelclass NotGiven: pass

以这种方式使用类是不寻常的,可能会造成混淆。意图 如果没有注释,代码将很难理解。它还会导致 这样的哨兵有一些意想不到的和不受欢迎的行为,例如 是可调用的。

定义一个推荐的“标准”习语,而不提供实现

大多数常见的现有习语都有明显的缺点。到目前为止,还没有成语 已经发现,在避免这些缺点的同时,清晰简洁。

此外,在关于此主题的民意调查  中,推荐 成语不受欢迎,得票最高的选项只有 25%的选民。

每个哨兵值的特定类型签名

很长一段时间以来,这个 PEP 的作者都努力为 特定于每个值的此类哨兵。领先的提案 (由 Guido 等人支持)是扩大 的使用 ,例如 .经过深思熟虑和讨论,尤其是在 typing-sig mailing list ,似乎所有这些解决方案都需要 特殊大小写和/或增加了静态类型实现的复杂性 跳棋,同时也限制了哨兵的实施。LiteralLiteral[MISSING]

因此,此 PEP 不再提出此类签名。取而代之的是,这个 PEP 建议用作 Sentinel 值的类型签名。Sentinel

有点不幸的是,静态类型检查器有时不会 因此能够推断出更具体的类型,例如在条件 块,如.但是,这是一个小问题 在实践中,可以很容易地进行类型检查器来理解这些情况 用。if value is not MISSING: ...typing.cast()

附注事项

  • 此 PEP 和初始实现是在专用 GitHub 中起草的 回购 。

  • 对于在类作用域中定义的哨兵,为避免潜在的名称冲突, 应该使用模块中变量的完全限定名称。只 最后一个句点之后的名称部分将用于默认值 repr.例如:

>>> class MyClass:...    NotGiven = sentinel('MyClass.NotGiven')>>> MyClass.NotGiven<NotGiven>
  • 在函数或方法中创建哨兵时应小心,因为 由同一模块中的代码创建的同名哨兵将是 相同。如果需要不同的哨兵对象,请确保使用 不同的名称。

版权

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

The End 微信扫一扫

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

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

上一篇 下一篇

相关阅读

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