warnings — Advertencias no fatales

Propósito:Entrega advertencias no fatales al usuario sobre los problemas encontrados al ejecutar un programa.

El módulo warnings fue introducido por PEP 230 como una forma de advertir a los programadores sobre los cambios en el lenguaje o las características de la biblioteca en previsión de cambios incompatibles con Python 3.0. También se puede usar para informar errores de configuración recuperables o degradación de características de bibliotecas faltantes. Sin embargo, es mejor entregar mensajes orientados al usuario a través del módulo logging, ya que las advertencias enviadas a la consola pueden perderse.

Como las advertencias no son fatales, un programa puede encontrar la misma situación de advertencia muchas veces en el transcurso de la ejecución. El módulo warnings suprime los mensajes repetidos de la misma fuente para reducir la molestia de ver la misma advertencia una y otra vez. La salida se puede controlar caso por caso, utilizando las opciones de línea de comando para el intérprete o llamando a las funciones que se encuentran en warnings.

Categorías y Filtrado

Las advertencias se clasifican usando subclases de la clase de excepción incorporada Warning. Se describen varios valores estándar en la documentación en línea para el módulo exceptions, y se pueden agregar advertencias personalizadas subclasificando Warning.

Las advertencias se procesan según la configuración de filtros. Un filtro consta de cinco partes: la acción, el mensaje, la categoría, el módulo y el número de línea. La parte mensaje del filtro es una expresión regular que se utiliza para coincidir con el texto de advertencia. La categoría es un nombre de una clase de excepción. El módulo contiene una expresión regular que se compara con el nombre del módulo que genera la advertencia. El número de línea se puede usar para cambiar el manejo en casos específicos de una advertencia.

Cuando se genera una advertencia, se compara con todos los filtros registrados. El primer filtro que coincide controla la acción tomada para la advertencia. Si ningún filtro coincide, se toma la acción predeterminada. Las acciones comprendidas por el mecanismo de filtrado se enumeran en la tabla a continuación.

Acciones de filtro de advertencia
Acción Significado
error Convierte la advertencia en una excepción.
ignore Descarta la advertencia.
always Siempre emite una advertencia.
default Imprime la advertencia la primera vez que se genera desde cada ubicación.
module Imprime la advertencia la primera vez que se genera cada módulo.
once Imprime la advertencia la primera vez que se genera.

Generando advertencias

La forma más simple de emitir una advertencia es llamar a warn() con el mensaje como argumento.

warnings_warn.py
import warnings

print('Before the warning')
warnings.warn('This is a warning message')
print('After the warning')

Luego, cuando se ejecuta el programa, se imprime el mensaje.

$ python3 -u warnings_warn.py

Before the warning
warnings_warn.py:13: UserWarning: This is a warning message
  warnings.warn('This is a warning message')
After the warning

Aunque se imprime la advertencia, el comportamiento predeterminado es continuar más allá de ese punto y ejecutar el resto del programa. Ese comportamiento se puede cambiar con un filtro.

warnings_warn_raise.py
import warnings

warnings.simplefilter('error', UserWarning)

print('Before the warning')
warnings.warn('This is a warning message')
print('After the warning')

En este ejemplo, la función simplefilter() agrega una entrada a la lista de filtros internos para indicarle al módulo warnings que genere una excepción cuando se emite una advertencia de UserWarning.

$ python3 -u warnings_warn_raise.py

Before the warning
Traceback (most recent call last):
  File "warnings_warn_raise.py", line 15, in <module>
    warnings.warn('This is a warning message')
UserWarning: This is a warning message

El comportamiento del filtro también se puede controlar desde la línea de comando utilizando la opción -W para el intérprete. Especifique las propiedades del filtro como una cadena con las cinco partes (acción, mensaje, categoría, módulo y número de línea) separadas por dos puntos (:). Por ejemplo, si warnings_warn.py se ejecuta con un filtro configurado para generar un error en UserWarning, se genera una excepción.

$ python3 -u -W "error::UserWarning::0" warnings_warn.py

Before the warning
Traceback (most recent call last):
  File "warnings_warn.py", line 13, in <module>
    warnings.warn('This is a warning message')
UserWarning: This is a warning message

Dado que los campos para mensaje y módulo se dejaron en blanco, se interpretaron como coincidentes con cualquier cosa.

Filtrado con patrones

Para filtrar mediante reglas más complejas mediante programación, usa filterwarnings(). Por ejemplo, para filtrar según el contenido del texto del mensaje, proporciona un patrón de expresión regular como argumento mensaje.

warnings_filterwarnings_message.py
import warnings

warnings.filterwarnings('ignore', '.*do not.*',)

warnings.warn('Show this message')
warnings.warn('Do not show this message')

El patrón contiene «do not», pero el mensaje real usa «Do no». El patrón coincide porque la expresión regular siempre se compila para buscar coincidencias entre mayúsculas y minúsculas.

$ python3 warnings_filterwarnings_message.py

warnings_filterwarnings_message.py:14: UserWarning: Show this
message
  warnings.warn('Show this message')

El siguiente programa de ejemplo genera dos advertencias.

warnings_filter.py
import warnings

warnings.warn('Show this message')
warnings.warn('Do not show this message')

Se puede ignorar una de las advertencias utilizando el argumento de filtro en la línea de comando.

$ python3 -W "ignore:do not:UserWarning::0" warnings_filter.py

warnings_filter.py:12: UserWarning: Show this message
  warnings.warn('Show this message')

Las mismas reglas de coincidencia de patrones se aplican al nombre del módulo fuente que contiene la llamada que genera la advertencia. Suprime todos los mensajes del módulo warnings_filter pasando el nombre del módulo como patrón al argumento módulo.

warnings_filterwarnings_module.py
import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
)

import warnings_filter

Dado que el filtro está en su lugar, no se emiten advertencias cuando se importa warnings_filter.

$ python3 warnings_filterwarnings_module.py

Para suprimir solo el mensaje en la línea 13 de warnings_filter, incluya el número de línea como último argumento para filterwarnings(). Usa el número de línea real del archivo fuente para limitar el filtro o 0 para que el filtro se aplique a todas las apariciones del mensaje.

warnings_filterwarnings_lineno.py
import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
    13,
)

import warnings_filter

El patrón coincide con cualquier mensaje, por lo que los argumentos importantes son el nombre del módulo y el número de línea.

$ python3 warnings_filterwarnings_lineno.py

.../warnings_filter.py:12: UserWarning: Show this message
  warnings.warn('Show this message')

Advertencias repetidas

Por defecto, la mayoría de los tipos de advertencias solo se imprimen la primera vez que ocurren en una ubicación determinada, con la «ubicación» definida por la combinación del módulo y el número de línea donde se genera la advertencia.

warnings_repeated.py
import warnings


def function_with_warning():
    warnings.warn('This is a warning!')


function_with_warning()
function_with_warning()
function_with_warning()

Este ejemplo llama a la misma función varias veces, pero produce una sola advertencia.

$ python3 warnings_repeated.py

warnings_repeated.py:14: UserWarning: This is a warning!
  warnings.warn('This is a warning!')

La acción "once" se puede usar para suprimir instancias del mismo mensaje desde diferentes ubicaciones.

warnings_once.py
import warnings

warnings.simplefilter('once', UserWarning)

warnings.warn('This is a warning!')
warnings.warn('This is a warning!')
warnings.warn('This is a warning!')

El texto del mensaje para todas las advertencias se guarda y solo se imprimen mensajes únicos.

$ python3 warnings_once.py

warnings_once.py:14: UserWarning: This is a warning!
  warnings.warn('This is a warning!')

Del mismo modo, "module" suprimirá los mensajes repetidos del mismo módulo, sin importar el número de línea.

Funciones alternativas de entrega de mensajes

Normalmente, las advertencias se imprimen en sys.stderr. Cambie ese comportamiento reemplazando la función showwarning() dentro del módulo warnings. Por ejemplo, para enviar advertencias a un archivo de registro en lugar de un error estándar, reemplaza showwarning() con una función que registre la advertencia.

warnings_showwarning.py
import warnings
import logging


def send_warnings_to_log(message, category, filename, lineno,
                         file=None, line=None):
    logging.warning(
        '%s:%s: %s:%s',
        filename, lineno,
        category.__name__, message,
    )


logging.basicConfig(level=logging.INFO)

old_showwarning = warnings.showwarning
warnings.showwarning = send_warnings_to_log

warnings.warn('message')

Las advertencias se emiten con el resto de los mensajes de registro cuando se llama a warn().

$ python3 warnings_showwarning.py

WARNING:root:warnings_showwarning.py:28: UserWarning:message

Formateo

Si las advertencias deben ir al error estándar, pero deben reformatearse, reemplaza formatwarning().

warnings_formatwarning.py
import warnings


def warning_on_one_line(message, category, filename, lineno,
                        file=None, line=None):
    return '-> {}:{}: {}:{}'.format(
        filename, lineno, category.__name__, message)


warnings.warn('Warning message, before')
warnings.formatwarning = warning_on_one_line
warnings.warn('Warning message, after')

La función de formato debe devolver una sola cadena que contenga la representación de la advertencia que se mostrará al usuario.

$ python3 -u warnings_formatwarning.py

warnings_formatwarning.py:19: UserWarning: Warning message,
before
  warnings.warn('Warning message, before')
-> warnings_formatwarning.py:21: UserWarning:Warning message,
after

Nivel de pila en advertencias

Por defecto, el mensaje de advertencia incluye la línea de origen que lo generó, cuando está disponible. Sin embargo, no siempre es útil ver la línea de código con el mensaje de advertencia real. En cambio, a warn() se le puede decir qué tan alto tiene que ir la pila para encontrar la línea que llamó a la función que contiene la advertencia. De esa manera, los usuarios de una función obsoleta pueden ver dónde se llama la función, en lugar de la implementación de la función.

warnings_warn_stacklevel.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
# encoding: utf-8

import warnings


def old_function():
    warnings.warn(
        'old_function() is deprecated, use new_function()',
        stacklevel=2)


def caller_of_old_function():
    old_function()


caller_of_old_function()

En este ejemplo, warn() necesita subir la pila dos niveles, uno para sí mismo y otro para old_function().

$ python3 warnings_warn_stacklevel.py

warnings_warn_stacklevel.py:14: UserWarning: old_function() is deprecated,
 use new_function()
  old_function()

Ver también