如何禁用然后重新启用警告?

2024-06-18 13:20:58 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在为Python库编写一些单元测试,并希望将某些警告作为异常引发,这可以通过simplefilter函数轻松完成。但是,对于一个测试,我想禁用警告,运行测试,然后重新启用警告。

我使用的是Python2.6,所以我应该可以使用catch_warnings上下文管理器来实现这一点,但它似乎对我不起作用。即使失败了,我也应该能够调用resetwarnings,然后重新设置过滤器。

下面是一个简单的例子来说明这个问题:

>>> import warnings
>>> warnings.simplefilter("error", UserWarning)
>>> 
>>> def f():
...     warnings.warn("Boo!", UserWarning)
... 
>>> 
>>> f() # raises UserWarning as an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UserWarning: Boo!
>>> 
>>> f() # still raises the exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UserWarning: Boo!
>>> 
>>> with warnings.catch_warnings():
...     warnings.simplefilter("ignore")
...     f()     # no warning is raised or printed
... 
>>> 
>>> f() # this should raise the warning as an exception, but doesn't
>>> 
>>> warnings.resetwarnings()
>>> warnings.simplefilter("error", UserWarning)
>>> 
>>> f() # even after resetting, I'm still getting nothing
>>> 

有人能解释一下我是怎么做到的吗?

编辑:显然这是一个已知的错误:http://bugs.python.org/issue4180


Tags: in警告asstdinlineexceptionerrorfile
3条回答

Brian Luft关于问题的起因是__warningregistry__是正确的。但我想澄清一件事:这个warnings模块的工作方式是为每个调用warn()的模块设置module.__warningregistry__。更复杂的是,warnings的stacklevel选项会导致为发出警告的模块设置属性,而不一定是调用warn()的模块。。。这取决于发出警告时的调用堆栈。

这意味着您可能有很多不同的模块,其中存在__warningregistry__属性,并且根据您的应用程序,在您再次看到警告之前,它们可能都需要清除。我一直依赖下面的代码片段来完成这项工作。。。它清除名称与regexp匹配的所有模块的警告注册表(默认为everything):

def reset_warning_registry(pattern=".*"):
    "clear warning registry for all match modules"
    import re
    import sys
    key = "__warningregistry__"
    for mod in sys.modules.values():
        if hasattr(mod, key) and re.match(pattern, mod.__name__):
            getattr(mod, key).clear()

更新:CPythonissue 21724地址问题表明resetwarnings()未清除警告状态。我在这个问题上附加了一个扩展的“上下文管理器”版本,可以从reset_warning_registry.py下载。

读了几遍文档,翻了几遍源代码和外壳,我想我已经找到了。文档可能会有所改进,以便更清楚地了解行为是什么。

警告模块在warnings registry保存一个注册表,以跟踪显示了哪些警告。如果在设置“error”筛选器之前注册表中没有列出警告(消息),则对warn()的任何调用都不会导致将消息添加到注册表中。此外,在第一次调用warn之前,似乎不会创建警告注册表:

>>> import warnings
>>> __warningregistry__
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
NameError: name '__warningregistry__' is not defined

>>> warnings.simplefilter('error')
>>> __warningregistry__
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
NameError: name '__warningregistry__' is not defined

>>> warnings.warn('asdf')
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
UserWarning: asdf

>>> __warningregistry__
{}

现在,如果忽略警告,它们将被添加到警告注册表:

>>> warnings.simplefilter("ignore")
>>> warnings.warn('asdf')
>>> __warningregistry__
{('asdf', <type 'exceptions.UserWarning'>, 1): True}
>>> warnings.simplefilter("error")
>>> warnings.warn('asdf')
>>> warnings.warn('qwerty')
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
UserWarning: qwerty

因此,错误筛选器将只应用于警告注册表中尚未出现的警告。要使代码正常工作,您需要在使用上下文管理器之后(或者通常在使用忽略筛选器并需要prev之后的任何时候)从警告注册表中清除相应的条目。用于提取错误筛选器的消息)。似乎有点不自然。。。

布赖恩对这件事很在行。所以您需要扩展catch_warnings来保存/恢复全局__warningregistry__

像这样的事情可能行得通

class catch_warnings_plus(warnings.catch_warnings):
    def __enter__(self):
        super(catch_warnings_plus,self).__enter__()
        self._warningregistry=dict(globals.get('__warningregistry__',{}))
    def __exit__(self, *exc_info):
        super(catch_warnings_plus,self).__exit__(*exc_info)
        __warningregistry__.clear()
        __warningregistry__.update(self._warningregistry)

相关问题 更多 >