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