Я пишу некоторые модульные тесты на библиотеку 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 "", line 1, in
File "", line 2, in f
UserWarning: Boo!
>>>
>>> f() # still raises the exception
Traceback (most recent call last):
File "", line 1, in
File "", 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
Перечитав несколько раз документацию и поковырявшись в исходниках и оболочке, я, кажется, разобрался. Документация, вероятно, может быть улучшена, чтобы прояснить поведение.
Модуль warnings ведет реестр __warningsregistry__, чтобы отслеживать, какие предупреждения были показаны. Если предупреждение (сообщение) не указано в реестре до установки фильтра '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
Таким образом, фильтр ошибок будет применяться только к тем предупреждениям, которых еще нет в реестре предупреждений. Чтобы ваш код работал, вам нужно будет очистить соответствующие записи из реестра предупреждений, когда вы закончите работу с контекстным менеджером (или вообще в любое время после того, как вы использовали фильтр игнорирования и хотите, чтобы предыдущее использованное сообщение было подхвачено фильтром ошибок). Кажется немного неинтуитивным...
Брайан замечен в __ реестре предупреждений __
. Поэтому вам нужно расширить 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)