Як я можу запевнитись, як це виняток (не лише для тестування)?


173

Я повинен зробити поліном Лагранжа в Python для проекту, який я роблю. Я роблю баріцентричний стиль, щоб уникнути використання явного for-loop на відміну від розділеного стилю різниці у стилі Ньютона. Проблема в мені полягає в тому, що мені потрібно спіймати поділ на нуль, але Python (або, можливо, нуме) просто робить попередження замість звичайного винятку.

Отже, що мені потрібно знати, як зробити це попередження, ніби це виняток. На пов'язані з цим питання, які я знайшов на цьому сайті, відповіли не так, як мені потрібно. Ось мій код:

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 має власну внутрішню архітектуру попередження на пітонах, яку можна спеціально контролювати (див. Відповідь Бакуріу).
gerrit

@gerrit. Я виправлений і дізнався нове. Я видалив свій оригінальний коментар, щоб уникнути несанкціонованості колекції значків.
Божевільний фізик

Ще один підхід, який ви можете використати, - це просто перевірити, чи знаменник 0 перед діленням, що дозволяє уникнути накладних помилок із системою попередження numpy. (Хоча це, мабуть, означає, що ви повинні розгорнути чітке розуміння списку на цикл, перевіряючи, чи будь-який із знаменників дорівнює нулю.)
Олівер,

Відповіді:


197

Здається, що ваша конфігурація використовує 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: 

Я думаю, що це початок. Але це насправді не вирішує мою проблему. Якщо я додаю warnings.warn (Warning ())) у свій код у блоці спробу, воно отримає попередження. Чомусь він не сприймає ділення за допомогою нульового попередження. Ось точне попереджувальне повідомлення: Попередження: діліться на нуль, що зустрічається у int_scalars
Джон К.

@JohnK. Ви повинні відредагувати своє запитання та додати точний результат, інакше ми не можемо сказати, що не так. Це може бути можливо , що NumPy визначає це попередження класу де - то і ви повинні першовідкривач в якому підпакету , щоб бути в змозі зловити його. Неважливо, я виявив, що вам слід користуватися RuntimeWarning. Оновлено відповідь.
Бакуріу

Ти впевнений? Я змінив код на використання, крім RuntimeWarning :. Це все ще не працює = /
Джон К.

@JohnK. У документації зазначено, що а RuntimeWarning. Проблема може полягати в тому, що ваша конфігурація numpy використовує printпараметр, який просто друкує попередження, але це не справжнє попередження, яким обробляється warningsмодуль ... Якщо це так, ви можете спробувати використати numpy.seterr(all='warn')і повторити спробу.
Бакуріу

3
У моїй версії numpy, ви не можете використовувати numpy.seterr(all='error'), це errorпотрібно raise.
грудня

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, так що поділ на нуль обробляється по-різному в тих випадках, коли чисельник є нульовими. Наприклад, у ряді 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'якщо ви хочете створити виняток з будь -якої помилки з плаваючою комою.


Помітно, це піднімає FloatingPointError, ні ZeroDivisionError.
Герріт

Це не працює на 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 () залежно від того, наскільки велика парасолька ви хочете подати з помилками лову.


3
відповідь =
1/0

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.