如何捕获像异常一样的numpy警告(不仅用于测试)?


173

我必须在Python中为正在执行的项目制作Lagrange多项式。我正在做一个重心样式,以避免使用显式的for循环,而不是牛顿的分差样式。我的问题是我需要用零除,但是Python(或者也许是numpy)只是将其警告而不是正常异常。

因此,我需要知道的是如何捕获此警告,就像它是一个例外一样。我在本网站上发现的与此相关的问题并未按照我需要的方式回答。这是我的代码:

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. 

执行此代码后,我得到的输出是:

Warning: divide by zero encountered in int_scalars

那是我要抓住的警告。它应该出现在列表理解中。


2
您确定是Warning: ...吗?尝试像np.array([1])/0RuntimeWarning: ...这样的输出。
Bakuriu

1
@MadPhysicist不是重复的;NumPy在Python之上具有自己的内部警告体系结构,可以对其进行专门控制(请参阅Bakuríu的回答)。
gerrit

@gerrit。我站得住脚,学到了新东西。我删除了原始评论,以避免触发徽章收集狂潮。
疯狂物理学家

您可以使用的另一种方法是在除法之前简单地检查分母是否为0,这避免了麻烦numpy的警告系统的麻烦。(尽管这可能意味着您必须将整洁的列表理解扩展为一个循环,以检查是否有任何分母为零。)
Oliver

Answers:


197

看来您的配置正在使用print选项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])

这意味着您看到的警告不是真正的警告,而只是打印了一些字符stdout(请参阅文档以获取信息seterr)。如果您想抓住它,可以:

  1. 使用numpy.seterr(all='raise')它将直接引发异常。但是,这会更改所有操作的行为,因此,这是行为上的很大变化。
  2. 使用numpy.seterr(all='warn'),可以将打印的警告转换为真实的警告,您将可以使用上述解决方案来本地化此行为更改。

实际warnings收到警告后,您可以使用该模块来控制警告的处理方式:

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

请仔细阅读文档,filterwarnings因为它可以使您仅过滤所需的警告并具有其他选项。我还要考虑看看catch_warnings哪个是上下文管理器,它会自动重置原始filterwarnings功能:

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

我认为这是一个开始。但这实际上并不能解决我的问题。如果在try块的代码中添加warnings.warn(Warning())),它将捕获警告。由于某些原因,它无法捕获除以零警告。这是确切的警告消息:警告:在int_scalars中遇到的零除
John K.

@JohnK。您应该编辑问题并添加确切的输出,否则我们无法分辨出问题所在。这也许是可能的,numpy的定义此警告类的地方,你必须在分装到discovere要能抓住它。没关系,我发现您应该使用RuntimeWarning。更新了答案。
Bakuriu

你确定吗?我更改了代码以使用RuntimeWarning:除外。它仍然无法正常工作= /
John K.

@JohnK。在文档中,它指出了a RuntimeWarning。问题可能是您的numpy配置正在使用该print选项,该选项仅显示警告,但不是由warnings模块处理的真实警告...如果是这种情况,您可以尝试使用numpy.seterr(all='warn')并重试。
Bakuriu

3
在我的版本中numpy,您不能使用numpy.seterr(all='error')error需要使用raise
2014年

41

在@Bakuriu的答案中添加一些内容:

如果您已经知道警告可能在何处发生,那么使用numpy.errstate上下文管理器通常会更干净一些,而不是 numpy.seterr将所有相同类型的后续警告视为相同,而不管它们在代码中的位置如何:

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

编辑:

在我最初的示例中,我有a = np.r_[0],但是显然numpy的行为发生了变化,使得在分子为全零的情况下对零除的处理方式有所不同。例如,在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

相应的警告消息也不同:1. / 0.记录为RuntimeWarning: divide by zero encountered in true_divide,而0. / 0.记录为RuntimeWarning: invalid value encountered in true_divide。我不确定为什么要进行此更改,但是我怀疑这与以下事实有关:0. / 0.是不能表示为数字(numpy的回报为NaN在这种情况下),而1. / 0.-1. / 0.分别返回+ Inf文件和-Inf ,符合IEE 754标准。

如果您想捕获两种类型的错误,则可以始终通过np.errstate(divide='raise', invalid='raise'),或者all='raise'如果您想对任何类型的浮点错误引发异常。


值得注意的是,它FloatingPointError没有ZeroDivisionError
gerrit

不适Python 3.6.3用于numpy==1.16.3。你能更新一下吗?
anilbey

1
@anilbey显然numpy的行为发生了变化,这意味着根据分子是否也(全部)为零,现在对零除的处理方式有所不同。
ali_m

27

为了详细说明上述@Bakuriu的答案,我发现这使我能够以类似于捕获错误警告的方式捕获运行时警告,从而很好地打印警告:

import warnings

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

您可能可以尝试放置warnings.catch_warnings()的位置,具体取决于您要用这种方式捕获错误的伞的大小。


3
答案=
1/0

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.