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

Eli*_*ght 12 python warnings

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

我正在使用Python 2.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
>>> 
Run Code Online (Sandbox Code Playgroud)

有人可以解释我是如何做到这一点的吗?

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

Bri*_*uft 11

阅读文档并几次阅读源代码和shell,我想我已经弄明白了.文档可能会改进,以使行为更清楚.

警告模块将注册表保存在__warningsregistry__,以跟踪已显示的警告.如果在设置"错误"过滤器之前未在注册表中列出警告(消息),则对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__
{}
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

因此,错误过滤器仅适用于警告注册表中尚未包含的警告.为了使您的代码工作,您需要在完成上下文管理器后清除警告注册表中的相应条目(或者通常在您使用忽略过滤器之后的任何时候,并希望使用上一个用过的消息被拿起错误过滤器).似乎有点不直观......


Eli*_*ins 8

Brian Luft __warningregistry__对于问题的原因是正确的.但我想澄清一点:道路warnings模块似乎工作是,它设置module.__warningregistry__每一个模块在那里warn()被调用.更复杂的是,stacklevel警告的选项导致为模块设置属性,警告是在"名称"中发出的,不一定warn()是被调用的那个......并且这取决于当时的调用堆栈发出警告.

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

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()
Run Code Online (Sandbox Code Playgroud)

更新:CPython 问题21724解决了resetwarnings()未清除警告状态的问题.我在此问题上附加了一个扩展的"上下文管理器"版本,可以从reset_warning_registry.py下载.


Joh*_*ooy 6

布赖恩是关于这个问题的__warningregistry__.所以,你需要延长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)
Run Code Online (Sandbox Code Playgroud)