Как мне поймать предупреждение numpy, как будто это исключение (не только для тестирования)?

Мне нужно сделать полином Лагранжа в 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

Это предупреждение, которое я хочу уловить. Это должно происходить внутри понимания списка.


person John K.    schedule 10.04.2013    source источник
comment
Вы уверены, что это Warning: ...? Пробуя такие вещи, как np.array([1])/0, я получаю RuntimeWarning: ... в качестве вывода.   -  person Bakuriu    schedule 10.04.2013
comment
@MadPhysicist Не дубликат; NumPy имеет собственную внутреннюю архитектуру предупреждений поверх Python, которую можно специально контролировать (см. ответ Бакуриу).   -  person gerrit    schedule 06.12.2016
comment
@геррит. Я исправился и узнал новую вещь. Я удалил свой первоначальный комментарий, чтобы не спровоцировать безумие по сбору значков.   -  person Mad Physicist    schedule 06.12.2016
comment
Другой подход, который вы могли бы использовать, - просто проверить, равен ли знаменатель 0 перед делением, что позволяет избежать накладных расходов на возню с системой предупреждения numpy. (Хотя это, вероятно, означало бы, что вам нужно расширить понимание аккуратного списка до цикла, проверяющего, равен ли какой-либо из знаменателей нулю.)   -  person Oliver    schedule 22.04.2019


Ответы (4)


Похоже, что ваша конфигурация использует параметр 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: 
person Bakuriu    schedule 10.04.2013
comment
Я думаю, это начало. Но на самом деле это не решает мою проблему. Если я добавлю warnings.warn(Warning())) в свой код в блоке try, он поймает предупреждение. По какой-то причине он не улавливает предупреждение о делении на ноль. Вот точное предупреждающее сообщение: Предупреждение: деление на ноль встречается в int_scalars. - person John K.; 10.04.2013
comment
@ДжонК. Вы должны отредактировать свой вопрос и добавить точный вывод, иначе мы не сможем сказать, что не так. Может быть возможно, что numpy где-то определяет этот класс предупреждений, и вам нужно выяснить, в каком подпакете можно его поймать. Неважно, я обнаружил, что вы должны использовать RuntimeWarning. Обновил ответ. - person Bakuriu; 10.04.2013
comment
Уверены ли вы? Я изменил свой код, чтобы использовать его, кроме RuntimeWarning:. Все равно не работает =/ - person John K.; 10.04.2013
comment
@ДжонК. В документации указано, что RuntimeWarning поднимается. Проблема может заключаться в том, что ваша конфигурация numpy использует параметр print, который просто выводит предупреждение, но это не настоящее предупреждение, обрабатываемое модулем warnings... Если это так, вы можете попробовать использовать numpy.seterr(all='warn') и повторить попытку. - person Bakuriu; 10.04.2013
comment
В моей версии numpy нельзя использовать numpy.seterr(all='error'), error должно быть raise. - person detly; 14.05.2014
comment
@detly Да, ты прав. Я не помню, разрешали ли старые версии 'error' вместо этого, если я написал это, потому что отвлекся. - person Bakuriu; 14.05.2014
comment
all = 'raise' вызывает FloatingPointException для всех типов предупреждений, что может быть нежелательно. Вместо этого вы можете определить исходный обработчик ошибок и использовать all = 'call'. См. Как отличить два исключения FloatingPointError от numpy? для подробностей. - person ynn; 20.07.2020

Чтобы немного добавить к ответу @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', если хотите создать исключение для любого типа ошибки с плавающей запятой.

person ali_m    schedule 13.11.2015
comment
Примечательно, что он повышает FloatingPointError, а не ZeroDivisionError. - person gerrit; 06.12.2016
comment
Это не работает на Python 3.6.3 с numpy==1.16.3. Не могли бы вы обновить его, пожалуйста? - person anilbey; 11.06.2019
comment
@anilbey Очевидно, в поведении numpy произошли изменения, которые означают, что деление на ноль теперь обрабатывается по-разному в зависимости от того, равен ли числитель (полностью) нулю. - person ali_m; 23.06.2019

Чтобы уточнить ответ @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() в зависимости от того, насколько большой зонт вы хотите создать с отловом ошибок таким образом.

person ntk4    schedule 07.04.2016
comment
answer = 1 / 0 вызовет ошибку независимо... вы могли бы привести правильный пример... - person Amirkhm; 28.11.2018

Удалите warnings.filterwarnings и добавьте:

numpy.seterr(all='raise')
person Shital Shah    schedule 20.12.2017
comment
Это самый простой ответ, рассматривающий предупреждение как обычное исключение. - person smörkex; 30.09.2020