¿Cómo atrapo una advertencia como si fuera una excepción (no solo para probar)?

174

Tengo que hacer un polinomio de Lagrange en Python para un proyecto que estoy haciendo. Estoy haciendo un estilo baricéntrico para evitar el uso de un bucle for explícito en lugar del estilo de diferencia dividida de Newton. El problema que tengo es que necesito atrapar una división por cero, pero Python (o quizás numpy) solo lo convierte en una advertencia en lugar de una excepción normal.

Entonces, lo que necesito saber hacer es captar esta advertencia como si fuera una excepción. Las preguntas relacionadas con esto que encontré en este sitio no fueron respondidas de la manera que necesitaba. Aquí está mi código:

import numpy as np
import matplotlib.pyplot as plt
import warnings

class Lagrange:
    def __init__(self, xPts, yPts):
        self.xPts = np.array(xPts)
        self.yPts = np.array(yPts)
        self.degree = len(xPts)-1 
        self.weights = np.array([np.product([x_j - x_i for x_j in xPts if x_j != x_i]) for x_i in xPts])

    def __call__(self, x):
        warnings.filterwarnings("error")
        try:
            bigNumerator = np.product(x - self.xPts)
            numerators = np.array([bigNumerator/(x - x_j) for x_j in self.xPts])
            return sum(numerators/self.weights*self.yPts) 
        except Exception, e: # Catch division by 0. Only possible in 'numerators' array
            return yPts[np.where(xPts == x)[0][0]]

L = Lagrange([-1,0,1],[1,0,1]) # Creates quadratic poly L(x) = x^2

L(1) # This should catch an error, then return 1. 

Cuando se ejecuta este código, el resultado que obtengo es:

Warning: divide by zero encountered in int_scalars

Esa es la advertencia que quiero atrapar. Debe ocurrir dentro de la lista de comprensión.

John K.
fuente
2
¿Estás seguro de que es así Warning: ...? Probar cosas como np.array([1])/0me sale RuntimeWarning: ...como salida.
Bakuriu
1
@MadPhysicist No es un duplicado; NumPy tiene su propia arquitectura de advertencia interna sobre Pythons, que puede controlarse específicamente (ver respuesta de Bakuríu).
gerrit
@gerrit. Me corrijo y aprendí algo nuevo. Eliminé mi comentario original para evitar desencadenar el frenesí de la colección de insignias.
Físico loco
Otro enfoque que podría usar es simplemente verificar si el denominador es 0 antes de la división, lo que evita la sobrecarga de jugar con el sistema de advertencia de Numpy. (Aunque esto probablemente significaría que tiene que expandir la comprensión de la lista ordenada en un bucle para verificar si alguno de los denominadores es cero.)
Oliver

Respuestas:

198

Parece que su configuración está utilizando la printopción para numpy.seterr:

>>> import numpy as np
>>> np.array([1])/0   #'warn' mode
__main__:1: RuntimeWarning: divide by zero encountered in divide
array([0])
>>> np.seterr(all='print')
{'over': 'warn', 'divide': 'warn', 'invalid': 'warn', 'under': 'ignore'}
>>> np.array([1])/0   #'print' mode
Warning: divide by zero encountered in divide
array([0])

Esto significa que la advertencia que ve no es una advertencia real, sino solo algunos caracteres impresos stdout(consulte la documentación para seterr). Si quieres atraparlo puedes:

  1. Uso numpy.seterr(all='raise')que aumentará directamente la excepción. Sin embargo, esto cambia el comportamiento de todas las operaciones, por lo que es un cambio bastante grande en el comportamiento.
  2. Use numpy.seterr(all='warn'), que transformará la advertencia impresa en una advertencia real y podrá utilizar la solución anterior para localizar este cambio de comportamiento.

Una vez que tenga una advertencia, puede usar el warningsmódulo para controlar cómo se deben tratar las advertencias:

>>> import warnings
>>> 
>>> warnings.filterwarnings('error')
>>> 
>>> try:
...     warnings.warn(Warning())
... except Warning:
...     print 'Warning was raised as an exception!'
... 
Warning was raised as an exception!

Lea atentamente la documentación filterwarningsya que le permite filtrar solo la advertencia que desea y tiene otras opciones. También consideraría mirar catch_warningscuál es un administrador de contexto que restablece automáticamente la filterwarningsfunción original :

>>> import warnings
>>> with warnings.catch_warnings():
...     warnings.filterwarnings('error')
...     try:
...         warnings.warn(Warning())
...     except Warning: print 'Raised!'
... 
Raised!
>>> try:
...     warnings.warn(Warning())
... except Warning: print 'Not raised!'
... 
__main__:2: Warning: 
Bakuriu
fuente
Creo que esto es un comienzo. Pero en realidad no soluciona mi problema. Si agrego warnings.warn (Warning ())) en mi código en el bloque try, detectará la advertencia. Por alguna razón, no captura la advertencia de división por cero. Aquí está el mensaje de advertencia exacto: Advertencia: dividir por cero encontrado en int_scalars
John K.
@JohnK. Debería editar su pregunta y agregar el resultado exacto, de lo contrario no podemos saber qué está mal. Que podría ser posible que numpy define esto en alguna clase de aviso y usted tiene que Discovere en el que subpaquete para poder cogerlo. No importa, descubrí que deberías usar RuntimeWarning. Se actualizó la respuesta.
Bakuriu
¿Estás seguro? Cambié mi código para usar excepto RuntimeWarning :. Todavía no funciona = /
John K.
@JohnK. En la documentación indica que RuntimeWarningse plantea a. El problema podría ser que su configuración numpy está utilizando la printopción, que simplemente imprime la advertencia pero no es una advertencia real manejada por el warningsmódulo ... Si este es el caso, puede intentar usarlo numpy.seterr(all='warn')e intentarlo nuevamente.
Bakuriu
3
En mi versión de numpy, no se puede usar numpy.seterr(all='error'), errordebe ser raise.
desvío
41

Para agregar un poco a la respuesta de @ Bakuriu:

Si ya sabe dónde es probable que ocurra la advertencia, a menudo es más limpio usar el numpy.errstateadministrador de contexto, en lugar de numpy.seterrtratar todas las advertencias posteriores del mismo tipo, independientemente de dónde ocurran dentro de su código:

import numpy as np

a = np.r_[1.]
with np.errstate(divide='raise'):
    try:
        a / 0   # this gets caught and handled as an exception
    except FloatingPointError:
        print('oh no!')
a / 0           # this prints a RuntimeWarning as usual

Editar:

En mi ejemplo original a = np.r_[0], sí, pero aparentemente hubo un cambio en el comportamiento de numpy de tal manera que la división por cero se maneja de manera diferente en los casos en que el numerador es todo ceros. Por ejemplo, en numpy 1.16.4:

all_zeros = np.array([0., 0.])
not_all_zeros = np.array([1., 0.])

with np.errstate(divide='raise'):
    not_all_zeros / 0.  # Raises FloatingPointError

with np.errstate(divide='raise'):
    all_zeros / 0.  # No exception raised

with np.errstate(invalid='raise'):
    all_zeros / 0.  # Raises FloatingPointError

Los mensajes de advertencia correspondientes también son diferentes: 1. / 0.se registra como RuntimeWarning: divide by zero encountered in true_divide, mientras que 0. / 0.se registra como RuntimeWarning: invalid value encountered in true_divide. No estoy seguro de por qué se realizó exactamente este cambio, pero sospecho que tiene que ver con el hecho de que el resultado de 0. / 0.no es representable como un número (numpy devuelve un NaN en este caso) mientras que 1. / 0.y -1. / 0.devuelve + Inf y -Inf respectivamente , según el estándar IEE 754.

Si desea detectar ambos tipos de error, siempre puede pasar np.errstate(divide='raise', invalid='raise'), o all='raise'si desea generar una excepción en cualquier tipo de error de coma flotante.

ali_m
fuente
Cabe destacar que plantea FloatingPointError, no ZeroDivisionError.
gerrit
Esto no funciona Python 3.6.3con numpy==1.16.3. ¿Podrías actualizarlo por favor?
anilbey
1
@anilbey Aparentemente hubo un cambio en el comportamiento de numpy que significa que la división por cero ahora se maneja de manera diferente dependiendo de si el numerador también es (todos) cero.
ali_m
27

Para profundizar en la respuesta de @ Bakuriu anterior, descubrí que esto me permite captar una advertencia de tiempo de ejecución de una manera similar a la que detectaría una advertencia de error, imprimiendo la advertencia muy bien:

import warnings

with warnings.catch_warnings():
    warnings.filterwarnings('error')
    try:
        answer = 1 / 0
    except Warning as e:
        print('error found:', e)

Probablemente podrás jugar con la colocación de la colocación warnings.catch_warnings () dependiendo de qué tan grande de paraguas quieras lanzar con errores de captura de esta manera.

ntk4
fuente
3
answer =
1/0
8

Elimine warnings.filterwarnings y agregue:

numpy.seterr(all='raise')
Shital Shah
fuente