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(无论是CPython、标准库还是第三方库)中的重大更改给这些API的用户。
为了改善这种情况,本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使用的通用开发工具,该警告的帮助性甚至更低,而对于仅仅碰巧用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的使用,以及如何在不实际运行提供API的代码或访问API的代码的情况下,根据warnings.warn调用推断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
最后修改时间:2025-02-01 08:55:40 GMT