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

174

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

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

Джон К.
источник
2
Вы уверены, что это Warning: ...? Попробовать такие вещи, как np.array([1])/0я получаю в RuntimeWarning: ...качестве вывода.
Бакуриу
1
@MadPhysicist Не дубликат; NumPy имеет собственную архитектуру внутренних предупреждений поверх Pythons, которой можно специально управлять (см. Ответ Bakuríu).
gerrit
@gerrit. Я исправился и узнал что-то новое. Я удалил свой исходный комментарий, чтобы избежать безумия при сборе значков.
Безумный физик
Другой подход, который вы могли бы использовать, это просто проверить, равен ли знаменатель 0 перед делением, что позволяет избежать ненужных затрат с системой предупреждений numpy. (Хотя это, вероятно, означало бы, что вы должны расширить понимание аккуратного списка в цикл, проверяющий, равен ли какой-либо из знаменателей нулю.)
Оливер

Ответы:

198

Похоже, что ваша конфигурация использует 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: 
Bakuriu
источник
Я думаю, что это начало. Но это на самом деле не решает мою проблему. Если я добавлю warnings.warn (Warning ())) в мой код в блоке try, он получит предупреждение. По некоторым причинам это не ловит предупреждение деления на ноль. Вот точное предупреждающее сообщение: Предупреждение: деление на ноль, встречающееся в int_scalars
Джон К.
@JohnK. Вы должны отредактировать свой вопрос и добавить точный результат, иначе мы не сможем сказать, что не так. Это может быть возможно , что NumPy определяет это предупреждение класса где - то и вы должны первооткрыватель в каком подпакете , чтобы быть в состоянии поймать его. Неважно, я обнаружил, что вы должны использовать RuntimeWarning. Обновил ответ.
Бакуриу
Ты уверен? Я изменил свой код, чтобы использовать, кроме RuntimeWarning :. Это все еще не работает = /
Джон К.
@JohnK. В документации говорится, что a RuntimeWarningподнято. Проблема может заключаться в том, что ваша пустая конфигурация использует printопцию, которая просто печатает предупреждение, но это не реальное предупреждение, обрабатываемое warningsмодулем ... Если это так, вы можете попробовать использовать numpy.seterr(all='warn')и повторить попытку.
Бакуриу
3
В моей версии numpy, вы не можете использовать numpy.seterr(all='error'), errorдолжно быть raise.
detly
41

Чтобы добавить немного к ответу @ 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.return + Inf и -Inf соответственно согласно стандарту IEE 754

Если вы хотите перехватить оба типа ошибок, вы всегда можете передать их np.errstate(divide='raise', invalid='raise'), или all='raise'если вы хотите вызвать исключение для любого типа ошибки с плавающей запятой.

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

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

ntk4
источник
3
ответ =
1/0
8

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

numpy.seterr(all='raise')
Шиталь шах
источник