2010-03-06 8 views
12

Estoy escribiendo algunas pruebas de unidad para una biblioteca de Python y me gustaría que ciertas advertencias se planteen como excepciones, lo que puedo hacer fácilmente con la función simplefilter. Sin embargo, para una prueba me gustaría desactivar la advertencia, ejecutar la prueba, luego volver a habilitar la advertencia.¿Cómo desactivo y luego vuelvo a habilitar una advertencia?

Estoy usando Python 2.6, así que se supone que puedo hacer eso con el administrador de contexto catch_warnings, pero parece que no funciona para mí. Incluso si eso falla, también podré llamar al resetwarnings y luego volver a configurar mi filtro.

He aquí un ejemplo sencillo que ilustra el problema:

>>> 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 
>>> 

¿Puede alguien explicar cómo puedo lograr esto?

EDIT: Al parecer se trata de un fallo conocido: http://bugs.python.org/issue4180

+1

Parece que puede haber algún error sutil en juego en el módulo de advertencias. Estaba jugando con su código en el shell y observé que incluso declarar otras funciones donde el mensaje de advertencia era idéntico tendría el mismo efecto (sin advertencia), mientras que la alteración del mensaje de advertencia le permite funcionar. –

Respuesta

11

Lectura a través de los documentos y cuantas veces y asomando alrededor de la fuente y la cáscara Creo que he descubierto. Los documentos probablemente podrían mejorar para aclarar cuál es el comportamiento.

El módulo de advertencias mantiene un registro en __warningsregistry__ para realizar un seguimiento de las advertencias que se han mostrado. Si una advertencia (mensaje) no aparece en el registro antes de que se establezca el filtro 'error', las llamadas a warn() no darán como resultado que el mensaje se agregue al registro. Además, no aparece el registro de alerta que se creará hasta que la primera llamada para advertir:

>>> 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 

Así el error:

>>> 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__ 
{} 
Ahora bien, si hacemos caso de las advertencias, que se agregarán al registro advertencias

el filtro solo se aplicará a las advertencias que aún no están en el registro de advertencias. Para hacer su trabajo de código que necesita para borrar las entradas correspondientes del registro de advertencias cuando haya terminado con el gestor de contexto (o en general cualquier momento después de haber usado el filtro de ignorar y quiere un prev. Mensaje utilizado para ser recogido el filtro de error). Parece un poco intuitivo ...

6

Brian se fija en el __warningregistry__. Así que hay que extender catch_warnings para salvar/restaurar el mundial __warningregistry__ demasiado

Algo como esto puede trabajar

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) 
+0

Estoy muy interesado en el uso de este código. ¿Respetará las advertencias previamente ignoradas? –

8

Brian Luft es correcta sobre __warningregistry__ ser la causa del problema. Pero quería aclarar una cosa: la forma en que el módulo de warnings parece funcionar es que establece module.__warningregistry__ para cada módulo donde warn() se llama. Para complicar las cosas aún más, la opción stacklevel a las advertencias hace que el atributo que se fijará para el módulo de la advertencia fue emitida "en nombre de", no necesariamente aquel en el que warn() se llamaba ... y eso es dependiente de la pila de llamadas en el Hora en que se emitió la advertencia.

Esto significa que puede tener muchos módulos diferentes donde está presente el atributo __warningregistry__ y, dependiendo de su aplicación, es posible que todos tengan que borrarse antes de volver a ver las advertencias. He estado confiando en el siguiente fragmento de código para lograr esto ...se borra el registro de advertencias para todos los módulos cuyo nombre coincida con la expresión regular (que por defecto es todo lo ):

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() 

Actualización: CPython issue 21724 cuestión direcciones que resetwarnings() no estado de advertencia clara. Adjunté una versión ampliada de "administrador de contexto" a este problema, puede descargarse desde reset_warning_registry.py.

2

A raíz de una aclaración útil Eli Collins, aquí es una versión modificada del gestor de catch_warnings contexto que borra el registro de advertencias en una determinada secuencia de módulos al entrar en el gestor de contexto, y restaura el registro de salida:

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]) 

uso con algo como:

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

me he encontrado con los mismos problemas, y mientras que todos los demás son válidas las respuestas que elegir una ruta diferente. No quiero probar el módulo de advertencias, ni saber sobre su funcionamiento interno. Así que sólo burlado en su lugar:

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() 

Este código es Python 3, el 2,6 que necesita el uso de una biblioteca externa de burla como unittest.mock solamente se añadió en 2,7.

Cuestiones relacionadas