问题:如何捕获像异常一样的numpy警告(不仅用于测试)?
我必须在Python中为正在执行的项目制作Lagrange多项式。我正在做一个重心样式,以避免使用显式的for循环,而不是牛顿的分差样式。我的问题是我需要用零除,但是Python(或者也许是numpy)只是将其警告而不是正常异常。
因此,我需要知道的是如何捕获此警告,就像它是一个exceptions一样。我在本网站上发现的与此相关的问题并未按照我需要的方式回答。这是我的代码:
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
那是我要抓住的警告。它应该出现在列表理解中。
回答 0
看来您的配置正在使用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')
它将直接引发异常。但是,这会更改所有操作的行为,因此,这是行为上的很大变化。 - 使用
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:
回答 1
在@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'
如果您想对任何类型的浮点错误引发异常。
回答 2
为了详细说明上述@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
删除warnings.filterwarnings并添加:
numpy.seterr(all='raise')