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

Python 如何禁用然后重新启用警告?,python,warnings,Python,Warnings,我正在为Python库编写一些单元测试,并希望某些警告作为异常出现,我可以轻松地使用该函数来完成。但是,对于一个测试,我想禁用警告,运行测试,然后重新启用警告 我使用的是Python2.6,所以我应该能够通过上下文管理器实现这一点,但它似乎不适合我。即使失败了,我也应该能够调用并重新设置我的过滤器 下面是一个简单的示例,说明了问题: >>> import warnings >>> warnings.simplefilter("error", UserWarni

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

我使用的是Python2.6,所以我应该能够通过上下文管理器实现这一点,但它似乎不适合我。即使失败了,我也应该能够调用并重新设置我的过滤器

下面是一个简单的示例,说明了问题:

>>> 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
>>> 
导入警告 >>>警告。simplefilter(“错误”,UserWarning) >>> >>>def(): ... 警告。警告(“Boo!”,用户警告) ... >>> >>>f()#引发UserWarning作为异常 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 文件“”,第2行,在f中 用户警告:嘘! >>> >>>f()#仍然引发异常 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 文件“”,第2行,在f中 用户警告:嘘! >>> >>>带有警告。捕获警告() ... 警告。simplefilter(“忽略”) ... f()#未提出或打印任何警告 ... >>> >>>f()#这应该作为例外发出警告,但不会 >>> >>>警告。重置警告() >>>警告。simplefilter(“错误”,UserWarning) >>> >>>f()#即使重置后,我仍然一无所获 >>> 有人能解释一下我是如何做到这一点的吗


编辑:显然这是一个已知的错误:

阅读文档,几次,然后在源代码和shell周围搜索,我想我已经找到了答案。这些文档可能会改进,以更清楚地说明行为是什么

“警告”模块在“警告注册表”中保留一个注册表,以跟踪显示了哪些警告。如果在设置“错误”筛选器之前注册表中未列出警告(消息),则对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__
{}
导入警告 >>>警告注册表__ ------------------------------------------------------------ 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 名称错误:未定义名称“\uuuu警告注册表” >>>警告。simplefilter('错误') >>>警告注册表__ ------------------------------------------------------------ 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 名称错误:未定义名称“\uuuu警告注册表” >>>warning.warn('asdf')) ------------------------------------------------------------ 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 用户警告:asdf >>>警告注册表__ {} 现在,如果忽略警告,它们将添加到警告注册表:

>>> 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
>警告。simplefilter(“忽略”)
>>>warning.warn('asdf'))
>>>警告注册表__
{('asdf',1):True}
>>>警告。simplefilter(“错误”)
>>>warning.warn('asdf'))
>>>警告。警告('qwerty')
------------------------------------------------------------
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
用户警告:qwerty

因此,错误筛选器将仅应用于警告注册表中尚未包含的警告。要使代码正常工作,您需要在使用上下文管理器时(或通常在使用忽略筛选器并希望从错误筛选器中提取prev.used消息后的任何时间)从警告注册表中清除相应的条目。似乎有点不直观

布赖恩对
\uuuuu警告注册表\uuuuuu
非常了解。因此,您需要扩展
catch\u warningregistry
来保存/恢复全局
\uuu warningregistry\uuu

像这样的东西可能有用

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)

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

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



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

根据Eli Collins的帮助说明,这里是一个修改版本的
catch_warnings
上下文管理器,它在进入上下文管理器时按给定的模块顺序清除警告注册表,并在退出时恢复注册表:

from warnings import catch_warnings

class catch_warn_reset(catch_warnings):
    """ Version of ``catch_warnings`` class that resets warning registry
    """
    def __init__(self, *args, **kwargs):
        self.modules = kwargs.pop('modules', [])
        self._warnreg_copies = {}
        super(catch_warn_reset, self).__init__(*args, **kwargs)

    def __enter__(self):
        for mod in self.modules:
            if hasattr(mod, '__warningregistry__'):
                mod_reg = mod.__warningregistry__
                self._warnreg_copies[mod] = mod_reg.copy()
                mod_reg.clear()
        return super(catch_warn_reset, self).__enter__()

    def __exit__(self, *exc_info):
        super(catch_warn_reset, self).__exit__(*exc_info)
        for mod in self.modules:
            if hasattr(mod, '__warningregistry__'):
                mod.__warningregistry__.clear()
            if mod in self._warnreg_copies:
                mod.__warningregistry__.update(self._warnreg_copies[mod])
与以下内容一起使用:

import my_module_raising_warnings
with catch_warn_reset(modules=[my_module_raising_warnings]):
    # Whatever you'd normally do inside ``catch_warnings``

我遇到了同样的问题,虽然所有其他答案都是有效的,但我选择了不同的路线。我不想测试warnings模块,也不想知道它的内部工作原理。所以我只是嘲笑它:

import warnings
import unittest
from unittest.mock import patch
from unittest.mock import call

class WarningTest(unittest.TestCase):
    @patch('warnings.warn')
    def test_warnings(self, fake_warn):
        warn_once()
        warn_twice()
        fake_warn.assert_has_calls(
            [call("You've been warned."),
             call("This is your second warning.")])

def warn_once():
    warnings.warn("You've been warned.")

def warn_twice():
    warnings.warn("This is your second warning.")

if __name__ == '__main__':
    __main__=unittest.main()

这段代码是Python3,对于2.6,您需要使用外部模拟库,因为unittest.mock只是在2.7中添加的。

似乎警告模块中有一些微妙的错误。我在shell中使用了您的代码,并注意到,即使声明警告消息相同的其他函数也会产生相同的效果(无警告),而更改警告消息可以使其工作。我对此代码的使用非常感兴趣。它会尊重以前被忽视的警告吗?
import warnings
import unittest
from unittest.mock import patch
from unittest.mock import call

class WarningTest(unittest.TestCase):
    @patch('warnings.warn')
    def test_warnings(self, fake_warn):
        warn_once()
        warn_twice()
        fake_warn.assert_has_calls(
            [call("You've been warned."),
             call("This is your second warning.")])

def warn_once():
    warnings.warn("You've been warned.")

def warn_twice():
    warnings.warn("This is your second warning.")

if __name__ == '__main__':
    __main__=unittest.main()