PEP 534 – 改进缺失标准库模块的错误信息
- 作者:
- Tomáš Orsava <tomas.n at orsava.cz>, Petr Viktorin <encukou at gmail.com>, Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 推迟
- 类型:
- 标准跟踪
- 创建日期:
- 2016年9月5日
- 发布历史:
摘要
Python 经常在没有完整标准库的情况下被构建或分发。然而,目前还没有一个标准且用户友好的方式来适当地告知用户导入缺失标准库模块的失败。
本 PEP 提出了一种识别预期标准库模块的机制,并在尝试导入标准库模块失败时向用户提供更具信息性的错误消息。
PEP 延期
PEP 作者目前并未积极致力于此 PEP,因此如果您对改进这些错误消息感兴趣,请联系我们! (例如,通过向 python-dev 邮件列表发帖)。
开放工作的关键部分是确定如何让 autoconf 和 Visual Studio 构建过程将预期和可选标准库模块列表填充到 sysconfig 元数据文件中。
动机
有几个用例只包含 Python 标准库的一个子集。然而,到目前为止,还没有一个用户友好的机制来告知用户 **为什么** 某个标准库模块缺失以及如何适当地补救这种情况。
CPython
当 Python 的某个标准库模块(例如 _sqlite3)在 CPython 构建期间由于缺少依赖项(例如 SQLite 头文件)而无法编译时,该模块会被简单地跳过。如果然后安装这个编译过的 Python 并尝试导入其中一个缺失的模块,Python 将会以 ModuleNotFoundError 失败。
例如,在故意从本地系统移除 sqlite-devel 之后
$ ./python -c "import sqlite3"
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/ncoghlan/devel/cpython/Lib/sqlite3/__init__.py", line 23, in <module>
from sqlite3.dbapi2 import *
File "/home/ncoghlan/devel/cpython/Lib/sqlite3/dbapi2.py", line 27, in <module>
from _sqlite3 import *
ModuleNotFoundError: No module named '_sqlite3'
这可能会让用户感到困惑,他们可能不明白为什么一个干净构建的 Python 会缺少标准库模块。
Linux及其他发行版
许多 Linux 及其他发行版已经将标准库的部分内容分离到独立的软件包中。最常被排除的模块包括 tkinter 模块,因为它引入了对图形环境的依赖;idlelib,因为它依赖于 tkinter(并且大多数 Linux 桌面环境都提供自己的默认代码编辑器);以及 test 包,因为它仅用于内部测试 Python,并且其大小与标准库的其余部分加起来差不多。
这些模块的省略方法有所不同。例如,Debian 修补了文件 Lib/tkinter/__init__.py,将行 import _tkinter 封装在一个 *try-except* 块中,并在遇到 ImportError 时简单地在错误消息中添加以下内容:please install the python3-tk package [1]。Fedora 和其他发行版则简单地不包含省略的模块,这可能会让用户对在哪里找到它们感到困惑。
一个来自 Fedora 29 的例子
$ python3 -c "import tkinter"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'tkinter'
规范
列出预期标准库模块的API
为了更容易识别哪些模块名称是 *预期* 在标准库中解析的,sysconfig 模块将扩展两个附加功能
sysconfig.get_stdlib_modules(),它将提供所有顶级 Python 标准库模块(包括私有模块)的名称列表sysconfig.get_optional_modules(),它将列出可选的公共顶级标准库模块名称
sysconfig.get_optional_modules() 的结果和现有的 sys.builtin_module_names 都将是新 sysconfig.get_stdlib_modules() 函数提供的完整列表的子集。
这些新增列表将在 Python 构建过程中生成,并与其他 sysconfig 值一起保存在 _sysconfigdata-*.py 文件中。
模块在“可选”列表中的可能原因将是
- 模块依赖于可选的构建依赖项(例如
_sqlite3,tkinter,idlelib) - 模块因其他原因而私有,因此可能并非所有实现都存在(例如
_freeze_importlib,_collections_abc) - 模块是平台特定的,因此可能并非所有安装都存在(例如
winreg) test包也可以自由地从 Python 运行时安装中省略,因为它旨在用于测试 Python 实现,而不是作为 Python 项目使用的运行时库(提供测试工具的公共 API 是unittest)
(注意:ensurepip、venv 和 distutils 模块在此 PEP 中均被视为强制模块,尽管并非所有重新分发者目前都遵循这种做法)
对默认 sys.excepthook 实现的更改
sys.excepthook 函数的默认实现将被修改,以便在检测到导入由两个新的 sysconfig 函数识别为属于 Python 标准库的模块失败时,分发适当的消息。
对于依赖可选构建依赖或在 Python 安装时被认为是可选的模块,修改后的错误消息
$ ./python -c "import sqlite3"
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/home/ncoghlan/devel/cpython/Lib/sqlite3/__init__.py", line 23, in <module>
from sqlite3.dbapi2 import *
File "/home/ncoghlan/devel/cpython/Lib/sqlite3/dbapi2.py", line 27, in <module>
from _sqlite3 import *
ModuleNotFoundError: Optional standard library module '_sqlite3' was not found
当整个顶级包缺失时,可选顶级包的子模块的修改错误消息
$ ./python -c "import test.regrtest"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: Optional standard library module 'test' was not found
当顶级包存在时,可选顶级包的子模块的修改错误消息
$ ./python -c "import test.regrtest"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'test.regrtest' in optional standard library module 'test'
对于总是期望可用的模块,修改后的错误消息
$ ./python -c "import ensurepip"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: Standard library module 'ensurepip' was not found
当顶级包存在时,标准库包的缺失子模块的修改错误消息
$ ./python -c "import encodings.mbcs"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'encodings.mbcs' in standard library module 'encodings'
这些修改后的错误消息明确指出,缺失的模块本应从标准库中获取,但由于某些原因不可用,而不是指示当前环境中缺少第三方依赖项。
设计讨论
修改 sys.excepthook
当引发的异常未被捕获并且程序即将退出或(在交互式会话中)控制权即将返回到提示符时,会调用 sys.excepthook 函数。这使其成为定制错误消息的完美场所,因为它不会影响已捕获的错误,因此不会减慢 Python 脚本的正常执行。
查询预期标准库模块名称的公共API
包含 sysconfig.get_stdlib_modules() 和 sysconfig.get_optional_modules() 函数将提供一种长期寻求的、易于列出 Python 标准库模块名称的方式 [2],这将(除其他好处外)使代码分析、性能分析和错误报告工具更容易提供运行时 --ignore-stdlib 标志。
仅包含顶级模块名称
本 PEP 建议新的查询 API 只报告顶级模块和包名称。这足以生成建议的错误消息,将所需条目数量减少一个数量级,并简化了构建过程中生成相关元数据的过程。
如果最终发现这过于限制,可以在查询 API 中添加一个新的 include_submodules 标志。然而,这 *不* 是初始提案的一部分,因为目前认为这样做的益处不足以证明额外的复杂性。
此限制有一个已知的后果,即新的默认 excepthook 实现将以报告真正缺失的标准库子模块的相同方式报告不正确的子模块名称
$ ./python -c "import unittest.muck"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No submodule named 'unittest.muck' in standard library module 'unittest'
将私有顶级模块名称列为可选标准库模块
许多具有可选外部构建依赖的模块都以混合模块的形式编写,其中有一个共享的 Python 包装器,围绕着对底层外部库的实现依赖接口。在其他情况下,私有顶级模块可能只是 CPython 的实现细节,而其他实现可能根本不提供该模块。
为了适当地报告涉及这些模块的导入错误,新的默认 excepthook 实现需要通过新的查询 API 报告它们。
延迟的想法
本节中的思想是本 PEP 可能会帮助实现的理念,但它们被认为超出了初始提案的范围。
平台相关模块
某些标准库模块可能缺失,因为它们仅在特定平台上提供。例如,winreg 模块仅在 Windows 上可用
$ python3 -c "import winreg"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'winreg'
在目前的提案中,这些平台相关的模块将简单地与所有其他可选模块一起包含,而不是试图以更结构化的方式公开平台依赖信息。
然而,为了 文档 的利益,平台依赖性至少在“Windows”、“Unix”、“Linux”和“FreeBSD”的层面被跟踪,因此似乎也可以通过编程方式暴露它。
当 __main__ 遮蔽标准库模块时发出警告
鉴于新的查询 API,新的默认 excepthook 实现可能会检测到 __main__.__file__ 或 __main__.__spec__.name 匹配标准库模块时,并发出适当的警告。
然而,实际执行任何此类操作都应该审查更多用户实际遇到此问题的情况,以及可能提供更多信息以协助调试情况的各种选项,而不是现在就需要纳入。
对下游发行版的建议
通过修补 site.py [*] 来提供自己的 sys.excepthook 函数实现,Python 发行商可以为任何未捕获的异常显示定制的错误消息,包括在遇到 ModuleNotFoundError 时告知用户安装缺失标准库模块的正确、特定于发行版的方式。
一些下游发行商已经在使用这种修补 sys.excepthook 的方法来集成平台崩溃报告机制。
向后兼容性
预计不会出现向后兼容性问题。已经修补 Python 模块以提供自定义缺失依赖处理的发行版可以继续不受阻碍地这样做。
参考和示例实现
待定。更详细的细节将取决于 CPython 构建系统的实际能力(其他实现届时应该能够使用生成的 CPython 数据,而不是自己重新生成)。
注释和参考文献
导致本 PEP 的想法已在 python-dev 邮件列表 和随后的 python-ideas 上讨论过。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0534.rst