Python 的预声明常量有点奇怪

Lobsters Hottest 新闻

摘要

深入探讨 Python 预声明常量的不一致行为,包括关键字状态、赋值限制和遮蔽(shadowing)怪癖。

<p><a href="https://lobste.rs/s/62kxco/python_s_pre_declared_constants_are_kinda">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/13 15:24

# python 的预声明常量有点奇怪 来源:https://sebsite.pw/w/20260801-pythonconstants.html python 有 6 个预声明的“常量”:`True`、`False`、`None`、`__debug__`、`Ellipsis`(或等价的 `...`)和 `NotImplemented`。但出于某种原因,它们的行为都略有不同。 ## `True`、`False` 和 `None` `True`、`False` 和 `None` 是关键字。它们不是标识符,而是直接作为独立的词法标记。这真的很奇怪;python 中没有任何其他东西是这样。通常事物是在常规名称解析过程中解析的,而不是在词法分析器本身中。 这样做的一个有趣副作用是,像 `x.True` 这样的表达式会抛出 `SyntaxError`。我很好奇这个决定背后的理由是什么(如果有的话)。 这些常量还有一些更有趣的地方,但我稍后再谈,因为它与其他常量有关。 ## `__debug__` `__debug__` 是一个布尔常量:它通常为 `True`,但在使用 `-O` 运行时为 `False`。这个思路类似于 `assert` 在非调试构建中被禁用:如果检查在“优化”构建中代价太高,你可以将代码包裹在 `if __debug__` 中,之类的。 不过 `__debug__` 真的很有趣,因为尽管它是一个普通标识符(不像 `True`、`False` 和 `None`),它是这门语言中唯一不能被赋值的标识符: `` >>> __debug__ = 67 File "", line 1 SyntaxError: cannot assign to __debug__ `` 你甚至不能把它作为属性来赋值: `` >>> x.__debug__ = 67 File "", line 1 SyntaxError: cannot assign to __debug__ `` 同样,没有其他标识符有这样的行为。这确实是一个特殊的特例。 但因为它不是关键字,它的行为与 `True`、`False` 和 `None` 略有不同:`x.__debug__` 会抛出 `AttributeError`(而不是 `SyntaxError`),因为它在语法上是有效的;它只是在查找一个不存在的属性。 有趣的是,试图删除 `__debug__` 时也会有一条特殊的错误消息(尽管如果没有这个特例,这本来会抛出 `NameError`),但这*不*适用于删除名为 `__debug__` 的属性: `` >>> del __debug__ File "", line 1 SyntaxError: cannot delete __debug__ >>> del x.__debug__ Traceback (most recent call last): File "", line 1, in NameError: name 'x' is not defined `` 如果 `x` 已定义,则会抛出 `AttributeError`。不管怎样,出于某种原因,这都不是 `SyntaxError`(不像赋值那样)。 ### 题外话:`SyntaxError` 是个谎言 说到错误:赋值给 `__debug__` 是我所知的少数几个情况之一,尽管某件事实际上并非无效语法,却会抛出 `SyntaxError`。你可以自己验证: `` >>> assert (__debug__ := 67) `` 在调试构建中运行该断言会抛出 `SyntaxError`,但在使用 `-O` 时,断言永远不会被编译,因此不会抛出异常。 另外两个这样的例子是在函数外使用 `yield` 或 `await`: `` >>> assert (yield) >>> assert (await 67) `` ## `Ellipsis` 和 `NotImplemented` `Ellipsis` 和 `NotImplemented` 在参考文档的“常量”部分有记载,但与另外 4 个常量不同,它们并不是“真正的”常量。它们只是普通的內建名称,所以可以被全局变量遮蔽: `` >>> NotImplemented = 67 >>> NotImplemented 67 `` 同样,我很好奇这里的设计理由。为什么它们不是特殊的,而其他常量却是特殊的? ## 覆写常量 这里有些有趣的事:尽管 `True`、`False` 和 `None` 是词法标记,它们也作为普通的內建对象存在: `` >>> import builtins >>> getattr(builtins, 'True') True >>> getattr(builtins, 'False') False >>> getattr(builtins, 'None') is None True `` 如果不使用 `getattr`,就没有办法直接访问这些。 但真正有趣的是:`setattr` 也能用! `` >>> setattr(builtins, 'True', 67) >>> getattr(builtins, 'True') 67 `` 然而,当使用词法标记访问时,这并不会改变其值: `` >>> True True `` 但 `__debug__` 也有同样的行为! `` >>> setattr(builtins, '__debug__', 67) >>> builtins.__debug__ 67 >>> __debug__ True `` 所以 `__debug__` 有点*可以被*赋值,但尽管它不是词法标记,它和 `True`、`False`、`None` 一样被特殊处理:它的值不受 builtins 模块变化的影响。所以它确实是一个常量! `Ellipsis` 和 `NotImplemented` 再次证明了它们实际上并不是常量: `` >>> setattr(builtins, 'Ellipsis', 67) >>> Ellipsis 67 `` 不过这并不会改变 `...` 的值: `` >>> ... Ellipsis `` 所以从某种意义上说,`...` 是一个真正的常量,但 `Ellipsis` 不是。很奇怪,对吧?

相似文章

Python 字符串字面量有点搞笑

Lobsters Hottest

一篇博客文章,探讨 Python 原始字符串字面量和 f-string 语法中的古怪行为,包括原始字符串不能以反斜杠结尾以及 f-string 表达式可以包含注释和多行的示例。

PHP 的古怪特性

Hacker News Top

一位开发者在使用了五年后反思 PHP 的古怪之处,重点介绍了其数组实现和类型系统的奇特之处。

关于NaN的两个案例分析

Lobsters Hottest

本文探讨了NaN在Python和Lua中的两种意外行为,其中对相等性和比较的隐含假设导致了令人惊讶的结果,例如Python列表相等性忽略了NaN的自不等性,以及Lua的for循环将NaN作为步长或限制时处理不当。