PEP 565 – 在 __main__ 中显示 DeprecationWarning
- 作者:
- Alyssa Coghlan <ncoghlan at gmail.com>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2017年11月12日
- Python 版本:
- 3.7
- 历史记录:
- 2017年11月12日,2017年11月25日
- 决议:
- Python-Dev 消息
摘要
在 Python 2.7 和 Python 3.2 中,默认警告过滤器已更新为默认隐藏 DeprecationWarning
,以便用 Python 编写的开发工具(例如 linter、静态分析器、测试运行程序、代码生成器)以及任何其他碰巧是用 Python 编写的应用程序中的弃用警告,除非用户明确选择查看它们,否则不会显示给其用户。
但是,此更改产生了不幸的副作用,导致 DeprecationWarning
在其主要预期目的方面效率显著降低:向这些 API 的用户提供有关 API 中(无论是在 CPython、标准库还是在第三方库中)的重大更改的预先通知。
为了改善这种情况,本 PEP 提出对默认警告过滤器进行一项调整:默认显示归因于主模块的弃用警告。
此更改意味着在交互式提示符处输入的代码和单个文件脚本中的代码将恢复为默认报告这些警告,而对于作为可导入模块的一部分分发的打包代码,这些警告将继续默认被静默处理。
本 PEP 还建议对参考解释器和标准库文档进行一些小的调整,以帮助使警告子系统更容易被新的 Python 开发人员使用。
作为文档更新的一部分,将更清楚地说明 unittest
测试运行程序在执行测试用例时默认显示所有警告,并且建议其他测试运行程序遵循此示例。
规范
新的默认警告过滤器条目
当前的一组默认警告过滤器包括
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::ImportWarning
ignore::BytesWarning
ignore::ResourceWarning
然后,默认的 unittest
测试运行程序使用 warnings.catch_warnings()
warnings.simplefilter('default')
在运行测试用例时覆盖默认过滤器。
本 PEP 中提出的更改是将默认警告过滤器列表更新为
default::DeprecationWarning:__main__
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::ImportWarning
ignore::BytesWarning
ignore::ResourceWarning
这意味着在警告的名义位置(由 warnings.warn
的 stacklevel
参数确定)位于 __main__
模块的情况下,每个 DeprecationWarning
的第一次出现将再次被报告。
此更改将导致 DeprecationWarning
默认显示
- 在交互式提示符处直接执行的代码
- 作为单个文件脚本的一部分直接执行的代码
同时继续默认隐藏
- 从
zipapp
档案的__main__.py
文件中的另一个模块导入的代码 - 从可执行包的
__main__
子模块中的另一个模块导入的代码 - 根据
console_scripts
或gui_scripts
入口点定义在安装时生成的执行脚本包装器中导入的代码
这意味着创建可安装或可执行工件(例如 zipapp
档案)以分发给其用户的工具开发人员不应该看到与现状有任何变化,而更多临时个人或本地分发脚本的用户可能会再次开始看到相关的弃用警告(就像他们在 Python 2.6 及更早版本中一样)。
FutureWarning
的其他用例
标准库文档将更新为明确建议使用 FutureWarning
(而不是 DeprecationWarning
)来表示旨在供应用程序的用户看到的向后兼容性警告。(这将是除了现有的使用 FutureWarning
来警告将来将保持有效代码但具有不同语义的构造之外)。
这将给出以下三种不同的向后兼容性警告类别,以及三个不同的目标受众
PendingDeprecationWarning
:默认情况下对所有代码隐藏。目标受众是积极关注确保其软件未来兼容性的 Python 开发人员(例如,具有特定支持义务的专业 Python 应用程序开发人员)。DeprecationWarning
:默认情况下报告在__main__
模块中直接运行的代码(因为此类代码被认为不太可能拥有专用的测试套件),但默认情况下对其他模块中的代码隐藏。目标受众是其软件可能因其依赖项升级(包括 Python 本身升级)而中断的 Python 开发人员(例如,使用 Python 编写脚本环境的开发人员,其中其他人控制着依赖项升级的时间)。FutureWarning
:默认情况下报告所有代码。目标受众是使用 Python 编写的应用程序的用户,而不是其他 Python 开发人员(例如,警告关于在配置文件格式中使用弃用设置)。
对于希望确保其 API 兼容性警告更可靠地被其用户看到的库和框架作者,建议在 Python 3.7 及更高版本中使用从 DeprecationWarning
派生的自定义警告类,在早期版本中使用从 FutureWarning
派生的自定义警告类。
测试运行程序的推荐过滤器设置
建议测试运行程序的开发人员在确定其默认警告过滤器时实现等效于以下内容的逻辑
if not sys.warnoptions:
warnings.simplefilter("default")
这实际上默认启用了所有警告,就像传递了 -Wd
命令行选项一样。
请注意,在测试套件中实际启用 BytesWarning
仍然需要在命令行中将 -b
选项传递给解释器。对于隐式字节转换和字节比较警告,警告过滤器机制仅用于确定是否应将其打印为警告或引发为异常 - 当未设置命令行标志时,解释器甚至不会发出警告。
交互式 Shell 的推荐过滤器设置
建议交互式 Shell 的开发人员添加一个过滤器,在输入和执行用户代码的命名空间中启用 DeprecationWarning
。
如果该命名空间是 __main__
(就像默认的 CPython REPL 一样),则除了本 PEP 中的更改之外,不需要进行任何更改。
使用除 __main__
之外的命名空间的交互式 Shell 实现将需要添加自己的过滤器。例如,IPython 使用以下命令([6])来设置合适的过滤器
warnings.filterwarnings("default", category=DeprecationWarning,
module=self.user_ns.get("__name__"))
其他文档更新
当前的警告系统参考文档在 -W
命令行选项或 PYTHONWARNINGS
环境变量的可能设置的具体示例方面相对较少,这些设置可以实现特定的最终结果。
以下改进建议作为本 PEP 的实现的一部分
- 在
PYTHONWARNINGS
环境变量的描述下明确列出以下条目PYTHONWARNINGS=error # Convert to exceptions PYTHONWARNINGS=always # Warn every time PYTHONWARNINGS=default # Warn once per call location PYTHONWARNINGS=module # Warn once per calling module PYTHONWARNINGS=once # Warn once per Python process PYTHONWARNINGS=ignore # Never warn
- 为
-W
命令行开关文档下列出的每个警告操作明确列出相应的简写选项(-We
、-Wa
、-Wd
、-Wm
、-Wo
、-Wi
) - 在
warnings
模块文档中明确列出默认过滤器集,使用action::category
和action::category:module
表示法 - 在
warnings.simplefilter
文档中明确列出以下代码段,作为在 Python 应用程序中默认关闭所有警告同时仍然允许通过PYTHONWARNINGS
或-W
命令行开关将其重新打开的推荐方法if not sys.warnoptions: warnings.simplefilter("ignore")
这些都不是新的(它们在所有仍在支持的 Python 版本中都已有效),但鉴于相关文档的当前结构,它们并不特别明显。
参考实现
参考实现可在与本 PEP 相关的跟踪问题[5]链接的 PR [4]中找到。
作为实现本 PEP 的副作用,内部警告过滤器列表将开始允许使用纯字符串作为过滤器定义的一部分(除了现有的编译正则表达式的使用之外)。当存在时,纯字符串将仅用于精确匹配。此方法允许在解释器启动期间添加新的默认过滤器,而无需提前访问 re
模块。
动机
如 [1] 中所讨论并在 [2] 中所提及,Python 2.7 和 Python 3.2 更改了 DeprecationWarning
的默认处理方式,以便
- 默认情况下,在正常代码执行期间隐藏警告。
unittest
测试运行器已更新,以便在运行测试时重新启用它。
这样做是为了避免出现以下类似工具输出的情况。
$ devtool mycode/
/usr/lib/python3.6/site-packages/devtool/cli.py:1: DeprecationWarning: 'async' and 'await' will become reserved keywords in Python 3.7
async = True
... actual tool output ...
即使devtool
是专为 Python 程序员设计的工具,但这也不是一个特别有用的警告,因为它会在每次调用时显示,即使最终用户可以采取的主要有帮助的步骤是向 devtool
的开发人员报告错误。
对于跨多种语言使用的通用开发工具,此警告甚至更没有帮助,并且对于碰巧是用 Python 编写的应用程序(不一定面向开发人员受众)几乎完全*没有*帮助。
但是,此更改被证明对以下受众产生了意外的后果。
- 任何使用除
unittest
内置的默认测试运行器之外的测试运行器的人(从未明确要求第三方测试运行器更改其默认警告过滤器,因此其中许多仍然依赖于旨在适合已部署应用程序的解释器默认值)。 - 任何使用默认
unittest
测试运行器在子进程中测试其 Python 代码的人(因为即使unittest
也只调整当前进程中的警告设置)。 - 任何在交互式提示符下或作为直接执行的脚本的一部分编写 Python 代码的人,这些脚本根本没有 Python 级别的测试套件。
在这些情况下,DeprecationWarning
最终变得几乎等同于 PendingDeprecationWarning
:它根本没有被看到。
PEP 范围的限制
本 PEP 的存在是为了专门解释提议添加到 3.7 默认警告过滤器中的内容,*以及*更清楚地阐明在 Python 2.7 和 3.2 中最初更改 DeprecationWarning
处理方式的理由。
本 PEP 并未解决当前处理弃用警告方法的所有已知问题。最值得注意的是
- 默认
unittest
测试运行器目前不报告在模块导入时发出的弃用警告,因为警告过滤器覆盖仅在测试执行期间实施,而不是在测试发现和加载期间实施。 - 默认
unittest
测试运行器目前不报告子进程中的弃用警告,因为警告过滤器覆盖直接应用于加载的warnings
模块,而不是应用于PYTHONWARNINGS
环境变量。 - 标准库没有提供一种简单的方法来选择加入查看在升级特定依赖项之前发出的所有警告(第三方
warn
模块 [3] 提供了此功能,但启用它需要修改标准库的warnings
模块)。 - 当软件已被分解成支持模块,但这些模块几乎没有或根本没有自动测试覆盖率时,在
__main__
中默认重新启用弃用警告不太可能帮助发现 API 兼容性问题。短期来看,目前最好的答案是使用PYTHONWARNINGS=default::DeprecationWarning
或python -W default::DeprecationWarning
运行受影响的应用程序,并注意其stderr
输出。从长远来看,这实际上是从事 Python 代码静态分析的研究人员的问题:如何可靠地找到弃用 API 的用法,以及如何根据warnings.warn
调用推断 API 或参数已弃用,而无需实际运行提供 API 的代码或访问它的代码。
虽然这些确实是现状的真正问题,但它们被排除在本 PEP 中的考虑范围之外,因为它们需要比默认警告过滤器中的单个附加条目更复杂的解决方案,并且解决它们至少可能不需要经过 PEP 流程。
对于任何有兴趣进一步探讨这些问题的人,前两个将是 unittest
模块增强请求,第三个将是 warnings
模块增强请求,而最后一个只有在从其内容推断 API 弃用被认为是一个难以解决的代码分析问题时,并且建议在注释中使用显式函数和参数标记语法时,才需要 PEP。
CPython 参考实现还将在 3.7 中包含以下相关更改。
- 一个新的
-X dev
命令行选项,它将多个以开发人员为中心的设置(包括-Wd
)组合到一个命令行标志中:https://github.com/python/cpython/issues/76224 - 更改调试版本中的行为,以显示在常规解释器版本中默认关闭的更多警告:https://github.com/python/cpython/issues/76269
独立于本 PEP 中对默认过滤器的提议更改,问题 32229 [7] 是一项提议,建议添加 warnings.hide_warnings
API,以便应用程序开发人员能够更轻松地在正常运行期间隐藏警告,同时在测试时轻松使其可见。
参考文献
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0565.rst