Подтвердить что ты не робот

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

Я должен сделать полином Лагранжа в Python для проекта, который я делаю. Я делаю барицентрический стиль, чтобы избежать использования явного for-loop, в отличие от однотонного разностного стиля Ньютона. Проблема в том, что мне нужно поймать деление на ноль, но 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

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

4b9b3361

Ответ 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])

Это означает, что предупреждение, которое вы видите, является not реальным предупреждением, но только некоторые символы напечатаны на 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: 

Ответ 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 в этом случае), тогда как 1./0. и -1./0. -1./0. вернуть +Inf и -Inf соответственно в соответствии со стандартом IEE 754.

Если вы хотите перехватить оба типа ошибок, вы всегда можете передать 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

Удалить предупреждения .filterwarnings и добавить:

numpy.seterr(all='raise')