我如何捕获 numpy 警告,就像它是一个异常一样(不仅仅是为了测试)?
- 2025-02-28 08:22:00
- admin 原创
- 76
问题描述:
我必须用 Python 为我正在做的一个项目制作一个拉格朗日多项式。我制作了一个重心式的拉格朗日多项式,以避免使用显式 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
这就是我想要捕获的警告。它应该出现在列表推导中。
解决方案 1:
您的配置似乎使用了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
)。如果您想捕获它,您可以:
使用
numpy.seterr(all='raise')
which 将直接引发异常。然而,这会改变所有操作的行为,因此这是一个相当大的行为变化。使用
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:
解决方案 2:
对@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),而根据 IEE 754 标准,1. / 0.
和-1. / 0.
分别返回 +Inf 和 -Inf。
如果您想捕获这两种类型的错误,您可以随时传递np.errstate(divide='raise', invalid='raise')
,或者如果您想对任何类型的浮点错误all='raise'
引发异常。
解决方案 3:
为了详细说明上述@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() 的位置,具体取决于您想通过这种方式捕获错误时要投射多大的伞。
解决方案 4:
删除 warnings.filterwarnings 并添加:
numpy.seterr(all='raise')
解决方案 5:
如果我可以建议这个选项,那么一个不错的 Pythonic 方法是使用上下文管理器。之前的另一个答案提出了这个想法,但我花了一些时间才弄清楚如何使用它,所以我补充了这个答案。
以前
Python 3.11
,您必须在不同的行上写下所需的操作(这是执行此操作的方法,我检查了文档,见下文)。之后
Python 3.11
,您可以以一种非常好的方式和简洁的方式将操作传递给上下文管理器的构造函数。
参见下面的示例。
# 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()
解决方案 6:
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)
这可能是一个更好的例子。
扫码咨询,免费领取项目管理大礼包!