Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 230 – 警告框架

作者:
Guido van Rossum <guido at python.org>
状态:
最终版
类型:
标准跟踪
创建日期:
2000年11月28日
Python 版本:
2.1
发布历史:
2000年11月5日

目录

摘要

本PEP提出了一个C和Python级别的API,以及命令行标志,用于发布警告消息并控制其处理方式。这主要基于GvR于2000年11月5日发布到python-dev的提案,并融入了Paul Prescod在同一天发布的反提案中的一些想法(例如使用类来对警告进行分类)。此外,尝试实施该提案也导致了一些小的调整。

动机

随着Python 3000的临近,除了错误之外,有必要开始对使用过时或已弃用的功能发出警告。从C和Python代码中,以及在编译时和运行时,都有许多其他原因可以发出警告。

警告并非致命,因此程序在一次执行中可能会多次触发相同的警告。如果程序发出无休止的相同警告流,那将是令人烦恼的。因此,需要一种机制来抑制多个相同的警告。

拥有用户控制哪些警告被打印出来也是可取的。虽然通常情况下,一直看到所有警告很有用,但在某些情况下,在生产程序中立即修复代码可能不切实际。在这种情况下,应该有一种方法来抑制警告。

在程序开发过程中能够抑制特定警告也很有用,例如当警告是由无法立即修复的第三方代码生成时,或者当无法修复代码时(可能针对一段完全正常的代码生成了警告消息)。在这种情况下提供抑制所有警告是不明智的:开发人员会错过关于代码其余部分的警告。

另一方面,也可以设想某些或所有警告最好被视为错误的情况。例如,某个特定的已弃用功能不应被使用可能是本地编码标准。为了强制执行此规则,能够将关于此特定功能的警告转换为错误并引发异常是有用的(不一定将所有警告都转换为错误)。

因此,我提议引入一个灵活的“警告过滤器”,它可以根据以下条件过滤掉警告或将它们转换为异常:

  • 它们在代码中生成的位置(按包、模块或函数)
  • 警告类别(警告类别将在下面讨论)
  • 特定的警告消息

警告过滤器必须可以通过命令行和Python代码进行控制。

发布警告的API

  • 从Python发出警告
    import warnings
    warnings.warn(message[, category[, stacklevel]])
    

    如果给出 category 参数,它必须是一个警告类别类(见下文);它默认为 warnings.UserWarning。如果发出的特定警告被警告过滤器更改为错误,这可能会引发异常。stacklevel 可由用 Python 编写的包装函数使用,如下所示

    def deprecation(message):
        warn(message, DeprecationWarning, level=2)
    

    这使得警告指向 deprecation() 的调用者,而不是 deprecation() 本身的原点(因为后者会违背警告消息的目的)。

  • 从 C 发出警告
    int PyErr_Warn(PyObject *category, char *message);
    

    正常返回 0,如果引发异常则返回 1(无论是由于警告转换为异常,还是由于实现中的故障,例如内存不足)。category 参数必须是警告类别类(见下文)或 NULL,在这种情况下它默认为 PyExc_RuntimeWarning。当 PyErr_Warn() 函数返回 1 时,调用者应进行正常的异常处理。

    PyErr_Warn() 的当前 C 实现导入警告模块(用 Python 实现)并调用其 warn() 函数。这最大限度地减少了为实现警告功能而需要添加的 C 代码量。

    [XXX 悬而未决的问题:在词法分析或解析期间发出警告怎么办,因为它们没有异常机制可用?]

警告类别

有许多内置异常代表警告类别。这种分类有助于过滤掉某些组的警告。目前定义了以下警告类别类:

  • Warning – 这是所有警告类别类的基类,它本身是 Exception 的子类
  • UserWarningwarnings.warn() 的默认类别
  • DeprecationWarning – 关于已弃用功能的警告的基类别
  • SyntaxWarning – 关于可疑语法功能的警告的基类别
  • RuntimeWarning – 关于可疑运行时功能的警告的基类别

[XXX:在本 PEP 的审查期间可能会提出其他警告类别。]

这些标准警告类别在 C 中以 PyExc_WarningPyExc_UserWarning 等形式提供。在 Python 中,它们在 __builtin__ 模块中提供,因此无需导入。

用户代码可以通过继承其中一个标准警告类别来定义其他警告类别。警告类别必须始终是 Warning 类的子类。

警告过滤器

警告过滤器控制警告是被忽略、显示还是转换为错误(引发异常)。

警告过滤器有三个方面:

  • 用于有效确定特定 warnings.warn()PyErr_Warn() 调用的处理方式的数据结构。
  • 从 Python 源代码控制过滤器的 API。
  • 控制过滤器的命令行开关。

警告过滤器分几个阶段工作。它针对(预期常见的)相同警告从代码中同一位置反复发出的情况进行了优化。

首先,警告过滤器收集发出警告的模块和行号;此信息可通过 sys._getframe() 轻松获取。

从概念上讲,警告过滤器维护一个有序的过滤器规范列表;任何特定警告都会依次与列表中的每个过滤器规范进行匹配,直到找到匹配项;匹配项决定了匹配的处理方式。每个条目都是一个元组,如下所示:

(category, message, module, lineno, action)
  • category 是一个类(warnings.Warning 的子类),警告类别必须是其子类才能匹配
  • message 是一个编译过的正则表达式,警告消息必须匹配(匹配不区分大小写)
  • module 是一个编译过的正则表达式,模块名必须匹配
  • lineno 是一个整数,警告发生的行号必须匹配,或 0 以匹配所有行号
  • action 是以下字符串之一
    • “error” – 将匹配的警告转换为异常
    • “ignore” – 永不打印匹配的警告
    • “always” – 始终打印匹配的警告
    • “default” – 打印每个发出警告位置的匹配警告的首次出现
    • “module” – 打印每个发出警告模块的匹配警告的首次出现
    • “once” – 只打印匹配警告的首次出现

由于 Warning 类派生自内置的 Exception 类,因此要将警告转换为错误,我们只需引发 category(message)

警告输出和格式化钩子

当警告过滤器决定发出警告时(但不是在决定引发异常时),它会传递关于函数 warnings.showwarning(message, category, filename, lineno) 的信息。此函数的默认实现将警告文本写入 sys.stderr,并显示文件名的源行。它有一个可选的第5个参数,可用于指定与 sys.stderr 不同的文件。

警告的格式化由一个单独的函数 warnings.formatwarning(message, category, filename, lineno) 完成。这会返回一个字符串(可能包含换行符并以换行符结尾),可以打印以获得与 showwarning() 函数相同的效果。

操作警告过滤器的API

warnings.filterwarnings(message, category, module, lineno, action)

这会检查参数的类型,编译消息和模块正则表达式,并将它们作为元组插入到警告过滤器前面。

warnings.resetwarnings()

将警告过滤器重置为空。

命令行语法

应该有命令行选项来指定最常见的过滤操作,我预计至少包括:

  • 抑制所有警告
  • 在所有地方抑制特定警告消息
  • 抑制特定模块中的所有警告
  • 将所有警告转换为异常

我提议以下命令行选项语法:

-Waction[:message[:category[:module[:lineno]]]]

其中

  • “action” 是允许操作的缩写(“error”、“default”、“ignore”、“always”、“once” 或 “module”)
  • “message” 是消息字符串;匹配消息文本是“message”初始子字符串的警告(匹配不区分大小写)
  • “category” 是标准警告类别类名的缩写 用户定义警告类别类的完全限定名,形式为 [package.]module.classname
  • “module” 是模块名(可能是 package.module)
  • “lineno” 是整数行号

除“action”之外的所有部分都可以省略,其中剥离空格后的空值与省略值相同。

解析 Python 命令行的 C 代码将所有 -W 选项的主体保存到字符串列表中,并作为 sys.warnoptions 提供给 warnings 模块。warnings 模块在首次导入时解析这些选项。在解析 sys.warnoptions 期间检测到的错误并非致命;消息会写入 sys.stderr,并继续处理该选项。

示例

-Werror
将所有警告转换为错误
-Wall
显示所有警告
-Wignore
忽略所有警告
-Wi:hello
忽略消息文本以“hello”开头的警告
-We::Deprecation
将弃用警告转换为错误
-Wi:::spam:10
忽略模块 spam 第 10 行的所有警告
-Wi:::spam -Wd:::spam:10
忽略模块 spam 中的所有警告,除了第 10 行
-We::Deprecation -Wd::Deprecation:spam
将弃用警告转换为错误,除了模块 spam 中

未解决的问题

我想到的一些悬而未决的问题

  • 在词法分析或解析期间发出警告怎么办,因为它们没有异常机制可用?
  • 提议的命令行语法有点丑陋(尽管简单的情况还不错:-Werror-Wignore 等)。有人有更好的主意吗?
  • 我有点担心过滤器规范过于复杂。也许只根据类别和模块(而不是消息文本和行号)进行过滤就足够了?
  • 模块名和文件名之间有点混淆。报告使用文件名,但过滤器规范使用模块名。也许它也应该允许文件名?
  • 我根本不相信包的处理方式是正确的。
  • 我们需要更多标准警告类别吗?更少?
  • 为了最大限度地减少启动开销,warnings 模块在首次调用 PyErr_Warn() 时导入。它在导入时解析 -W 选项的命令行。因此,可能没有警告的程序不会抱怨无效的 -W 选项。

被拒绝的关注点

Paul Prescod、Barry Warsaw 和 Fred Drake 提出了一些我认为不关键的额外问题。我在此处处理它们(这些问题是转述的,并非他们的原话)

  • Paul:warn() 应该是一个内置函数或一个语句,以便易于使用。

    回复:“from warnings import warn” 已经足够简单。

  • Paul:如果我有一个速度关键的模块,在内部循环中触发警告怎么办?应该能够禁用检测警告的开销(而不仅仅是抑制警告)。

    回复:重写内部循环以避免触发警告。

  • Paul:如果我想查看警告的完整上下文怎么办?

    回复:使用 -Werror 将其转换为异常。

  • Paul:我更喜欢 “:*:*:” 而不是 “:::” 来省略警告规范的部分。

    回复:我不喜欢。

  • Barry:如果 lineno 可以是范围规范就更好了。

    回复:已经太复杂了。

  • Barry:我想添加我自己的警告操作。也许如果“action”可以是可调用对象,也可以是字符串。那么在我的 IDE 中,我可以将其设置为“mygui.popupWarningsDialog”。

    回复:为此,您将覆盖 warnings.showwarning()

  • Fred:为什么 Warning 类别类必须在 __builtin__ 中?

    回复:这是最简单的实现,考虑到在第一次 PyErr_Warn() 调用(它会导入 warnings 模块)之前,警告类别必须在 C 中可用。我认为将它们作为内置功能提供没有问题。

实施

这里有一个原型实现:http://sourceforge.net/patch/?func=detailpatch&patch_id=102715&group_id=5470


来源:https://github.com/python/peps/blob/main/peps/pep-0230.rst

最后修改:2025-02-01 08:55:40 GMT