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标准库的子集有几个用例。但是,到目前为止,还没有用户友好的机制来告知用户为什么缺少stdlib模块以及如何适当地解决此问题。
CPython
当Python的标准库模块之一(例如_sqlite3
)由于缺少依赖项(例如SQLite头文件)而无法在CPython构建期间编译时,该模块将被简单地跳过。如果您随后安装此编译的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
上次修改时间:2023-10-11 12:05:51 GMT