How do I catch a numpy warning like it's an exception (not just for testing)?
Asked Answered
W

6

243

I have to make a Lagrange polynomial in Python for a project I'm doing. I'm doing a barycentric style one to avoid using an explicit for-loop as opposed to a Newton's divided difference style one. The problem I have is that I need to catch a division by zero, but Python (or maybe numpy) just makes it a warning instead of a normal exception.

So, what I need to know how to do is to catch this warning as if it were an exception. The related questions to this I found on this site were answered not in the way I needed. Here's my code:

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. 

When this code is executed, the output I get is:

Warning: divide by zero encountered in int_scalars

That's the warning I want to catch. It should occur inside the list comprehension.

Writeup answered 10/4, 2013 at 18:36 Comment(4)
Are you quite sure it's Warning: ...? Trying things like np.array([1])/0 I get RuntimeWarning: ... as output.Greenleaf
@MadPhysicist Not a duplicate; NumPy has its own internal warning architecture on top of Pythons, which can be specifically controlled (see answer by Bakuríu).Dulcedulcea
@gerrit. I stand corrected and learned a new thing. I deleted my original comment to avoid triggering badge collection frenzy.Oink
Another approach you could use is to simply check if the denominator is 0 before division, which avoids the overhead of fiddling with numpy's warning system. (Although this would probably mean you have to expand the neat list comprehension into a loop checking if any of the denominators is zero.)Hind
G
262

It seems that your configuration is using the print option for 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])

This means that the warning you see is not a real warning, but it's just some characters printed to stdout(see the documentation for seterr). If you want to catch it you can:

  1. Use numpy.seterr(all='raise') which will directly raise the exception. This however changes the behaviour of all the operations, so it's a pretty big change in behaviour.
  2. Use numpy.seterr(all='warn'), which will transform the printed warning in a real warning and you'll be able to use the above solution to localize this change in behaviour.

Once you actually have a warning, you can use the warnings module to control how the warnings should be treated:

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

Read carefully the documentation for filterwarnings since it allows you to filter only the warning you want and has other options. I'd also consider looking at catch_warnings which is a context manager which automatically resets the original filterwarnings function:

>>> 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: 
Greenleaf answered 10/4, 2013 at 18:53 Comment(7)
I think this is a start. But it doesn't actually fix my problem. If I add warnings.warn(Warning())) in my code in the try block, it'll catch the warning. For some reason it doesn't catch the divide by zero warning. Here's the exact warning message: Warning: divide by zero encountered in int_scalarsWriteup
@JohnK. You should edit your question and add the exact output, otherwise we cannot tell what's wrong. It might be possible that numpy defines this warning class somewhere and you have to discovere in which subpackage to be able to catch it. Never mind, I discovered that you should use RuntimeWarning. Updated the answer.Greenleaf
Are you sure? I changed my code to use except RuntimeWarning:. It still isn't working =/Writeup
@JohnK. In the documentation it states that a RuntimeWarning is raised. The problem might be that your numpy configuration is using the print option, which simply prints the warning but it's not a real warning handled by the warnings module... If this is the case you could try to use numpy.seterr(all='warn') and try again.Greenleaf
In my version of numpy, you can't use numpy.seterr(all='error'), error needs to be raise.Koweit
@Koweit Yes, you are right. I don't remember whether old versions allowed 'error' instead, of if I wrote that because I was distracted.Greenleaf
all = 'raise' raises FloatingPointException for all types of warnings, which may not be preferable. Rather, you may want to define an original error handler and use all = 'call'. See How can I distinguish between two numpy FloatingPointError exceptions? for the detail.Sitwell
B
69

To add a little to @Bakuriu's answer:

If you already know where the warning is likely to occur then it's often cleaner to use the numpy.errstate context manager, rather than numpy.seterr which treats all subsequent warnings of the same type the same regardless of where they occur within your code:

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

Edit:

In my original example I had a = np.r_[0], but apparently there was a change in numpy's behaviour such that division-by-zero is handled differently in cases where the numerator is all-zeros. For example, in 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

The corresponding warning messages are also different: 1. / 0. is logged as RuntimeWarning: divide by zero encountered in true_divide, whereas 0. / 0. is logged as RuntimeWarning: invalid value encountered in true_divide. I'm not sure why exactly this change was made, but I suspect it has to do with the fact that the result of 0. / 0. is not representable as a number (numpy returns a NaN in this case) whereas 1. / 0. and -1. / 0. return +Inf and -Inf respectively, per the IEE 754 standard.

If you want to catch both types of error you can always pass np.errstate(divide='raise', invalid='raise'), or all='raise' if you want to raise an exception on any kind of floating point error.

Blythebm answered 13/11, 2015 at 21:21 Comment(4)
Notably it raises FloatingPointError, not ZeroDivisionError.Dulcedulcea
This does not work on Python 3.6.3 with numpy==1.16.3. Could you update it please?Pharmacist
@Pharmacist Apparently there was a change in numpy's behaviour that means division-by-zero is now handled differently depending on whether the numerator is also (all) zero.Blythebm
Why does a RunTimeWarning become a FloatingPointError?Untrue
A
35

To elaborate on @Bakuriu's answer above, I've found that this enables me to catch a runtime warning in a similar fashion to how I would catch an error warning, printing out the warning nicely:

import warnings

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

You will probably be able to play around with placing of the warnings.catch_warnings() placement depending on how big of an umbrella you want to cast with catching errors this way.

Artamas answered 7/4, 2016 at 23:40 Comment(1)
answer = 1 / 0 will raise an error independently... you could give a correct example...Secretariat
C
22

Remove warnings.filterwarnings and add:

numpy.seterr(all='raise')
Cherimoya answered 20/12, 2017 at 4:46 Comment(1)
This is the easiest answer, treating the warning like a normal exceptionTriplenerved
T
2

If I may suggest this option, a nice Pythonic way of doing it is using context managers. Another previous answer offered this idea but it took me a bit of time to figure out how to use it so I complement the answer.

  • Before Python 3.11, you had to write the desired action on a different line (and it is the way to do it, I checked in the docs, see below).

  • After Python 3.11, you can in a very nice way and concise way pass the action to the constructor of the context manager.

See example below.

# works in python 3.8: https://docs.python.org/3.8/library/warnings.html
import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fn_triggering_warnings()

# works in python 3.11: https://docs.python.org/3.11/library/warnings.html
import warnings
with warnings.catch_warnings(action="ignore"):
    fn_triggering_warnings()
Thurmond answered 18/12, 2023 at 22:26 Comment(0)
E
0
from statsmodels.stats.weightstats import ztest

with warnings.catch_warnings():
    warnings.filterwarnings('error')
    try:
        zstat, pvalue = ztest([0,0], [0], alternative='two-sided')
    except Warning as e:
        print('error found:', e)

This could be a better example.

Edessa answered 3/12, 2023 at 14:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.