关于python:如何捕获一个numpy警告,就像它是一个例外(不仅仅是测试)?

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

我必须在Python中为我正在做的项目制作拉格朗日多项式。 我正在做一个重心的样式,以避免使用显式的for循环而不是Newton的分割差异样式。 我遇到的问题是我需要将除法除以零,但Python(或者可能是numpy)只是使它成为警告而不是正常的异常。

所以,我需要知道的是抓住这个警告,好像它是一个例外。 我在本网站上发现的相关问题没有按照我需要的方式回答。 这是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.

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

1
Warning: divide by zero encountered in int_scalars

这是我想要捕捉的警告。 它应该出现在列表理解中。


您的配置似乎正在使用numpy.seterrprint选项:

1
2
3
4
5
6
7
8
9
>>> 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的文档)。如果你想抓住它,你可以:

  • 使用numpy.seterr(all='raise')将直接引发异常。然而,这会改变所有操作的行为,因此这是一个相当大的行为变化。
  • 使用numpy.seterr(all='warn'),它将在真实警告中转换打印的警告,您将能够使用上述解决方案来本地化此行为更改。
  • 实际发出警告后,您可以使用warnings模块控制警告的处理方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> 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函数的上下文管理器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> 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的回答:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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.和< x19>根据IEE 754标准分别返回+ Inf和-Inf。

    如果要捕获这两种类型的错误,如果要在任何类型的浮点错误上引发异常,则可以始终传递np.errstate(divide='raise', invalid='raise')all='raise'


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

    1
    2
    3
    4
    5
    6
    7
    8
    import warnings

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

    您可能可以使用warnings.catch_warnings()放置,这取决于您希望通过这种方式捕获错误来构建多大的伞。


    删除warnings.filterwarnings并添加:

    1
    numpy.seterr(all='raise')