En Python, ¿cómo se detectan las advertencias como si fueran excepciones?

103

Una biblioteca de terceros (escrita en C) que uso en mi código Python está emitiendo advertencias. Quiero poder usar la try exceptsintaxis para manejar adecuadamente estas advertencias. ¿Hay alguna forma de hacer esto?

Boris Gorelik
fuente
2
¿Son esas advertencias solo mensajes de texto escritos en stderr?
Fenikso
1
Fenikso: No lo sé con certeza, parecen advertencias reales
Boris Gorelik
1
¿Cómo reconoce la "advertencia real"? Pensé que en C recibes una advertencia real durante la compilación.
Fenikso
warnings.filterwarningshace exactamente lo que quieres, no entiendo cuál es tu problema?
Rosh Oxymoron
4
@Fenikso, @Rosh Oxymoron tenías razón. Mi error. warnings.filterwarnigns('error')hace el trabajo. No puedo encontrar la respuesta original que propuso esta solución
Boris Gorelik

Respuestas:

51

Para citar del manual de Python ( 27.6.4. Advertencias de prueba ):

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)
Bobby Powers
fuente
6
Aquí hay una respuesta que le dice cómo usar la try exceptsintaxis.
Unapiedra
Esto tiene la ventaja, sobre la respuesta de niekas, de que si fnxdevuelve algo, conserva ese resultado (y aún puede administrar la advertencia).
Pietro Battiston
Esto no responde a la pregunta del OP, que trataba de manejar wanrings, no de probarlos. Sin embargo, la respuesta de niekas a continuación muestra cómo manejar las advertencias.
Biggsy
Solo tenga en cuenta que la función anterior no funcionará si su función solo devuelve una advertencia de forma intermitente porque en el caso de que fxn()no devuelva una advertencia, whabrá una lista vacía. Si wes una lista vacía (es decir []), a continuación, ejecutar el código le dará el siguiente error: IndexError: list index out of range. Si solo está buscando formatear o verificar las propiedades de las advertencias capturadas, entonces es mejor usar un bucle for:for x in w: print(f'{x.category.__name__}: {str(x.message)}')
Steven M. Mortimer
130

Para manejar las advertencias como errores, simplemente use esto:

import warnings
warnings.filterwarnings("error")

Después de esto, podrá detectar las mismas advertencias que los errores, por ejemplo, esto funcionará:

try:
    some_heavy_calculations()
except RuntimeWarning:
    import ipdb; ipdb.set_trace()

PD Agregó esta respuesta porque la mejor respuesta en los comentarios contiene errores ortográficos: en filterwarnignslugar de filterwarnings.

niekas
fuente
8
Y si solo desea ver un seguimiento de la pila, las dos primeras líneas son todo lo que necesita.
z0r
5
Esto es perfecto. Solo quería que mi script detuviera la ejecución tan pronto como se emitiera la advertencia, para poder imprimir información de depuración relevante y solucionar el problema.
Praveen
1
No necesita la filterwarningsllamada para capturar Warnings, al menos en Python 3. simplemente funciona.
naught101
1
La respuesta aceptada no responde a la pregunta del OP. Esta respuesta lo hace. Esta es la respuesta que estaba buscando cuando mi búsqueda encontró esta pregunta.
Biggsy
15

Aquí hay una variación que aclara cómo trabajar solo con sus advertencias personalizadas.

import warnings
with warnings.catch_warnings(record=True) as w:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")

    # Call some code that triggers a custom warning.
    functionThatRaisesWarning()

    # ignore any non-custom warnings that may be in the list
    w = filter(lambda i: issubclass(i.category, UserWarning), w)

    if len(w):
        # do something with the first warning
        email_admins(w[0].message)
mcqwerty
fuente
4

En algunos casos, es necesario utilizar ctypes para convertir las advertencias en errores. Por ejemplo:

str(b'test')  # no error
import warnings
warnings.simplefilter('error', BytesWarning)
str(b'test')  # still no error
import ctypes
ctypes.c_int.in_dll(ctypes.pythonapi, 'Py_BytesWarningFlag').value = 2
str(b'test')  # this raises an error
Collin Anderson
fuente
Esta respuesta es constructiva simplemente para mostrar cómo cometer errores solo en ciertos tipos de advertencia. Para casi cualquier proyecto de software grande, si lo hace warnings.simplefilter('error'), no obtendrá el rastreo de la advertencia que vio en los registros, sino que obtendrá los rastreos de las advertencias previamente filtradas. El uso simplefiltertambién es la forma más rápida de llegar a su respuesta si tiene alguna invocación de CLI.
AlanSE