Jak złapać ostrzeżenie, jakby było to wyjątek (nie tylko do testowania)?

173

Muszę utworzyć wielomian Lagrange'a w Pythonie dla projektu, który robię. Robię styl barycentryczny, aby uniknąć używania jawnej pętli for w przeciwieństwie do stylu podzielonej różnicy Newtona. Problem, który mam, polega na tym, że muszę złapać dzielenie przez zero, ale Python (lub może numpy) po prostu sprawia, że ​​jest to ostrzeżenie zamiast normalnego wyjątku.

Tak więc muszę wiedzieć, jak to zrobić, to złapać to ostrzeżenie, jakby to był wyjątek. Odpowiedzi na pytania związane z tym, które znalazłem na tej stronie, nie były potrzebne. Oto mój kod:

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. 

Po wykonaniu tego kodu otrzymuję następujące dane:

Warning: divide by zero encountered in int_scalars

To ostrzeżenie, które chcę złapać. Powinien pojawić się wewnątrz rozumienia listy.

John K.
źródło
2
Jesteś pewien, że tak jest Warning: ...? Próbuję rzeczy takich jak np.array([1])/0otrzymuję RuntimeWarning: ...jako wyjście.
Bakuriu,
1
@MadPhysicist To nie jest duplikat; NumPy ma własną wewnętrzną architekturę ostrzegawczą oprócz Pythonów, którą można specjalnie kontrolować (patrz odpowiedź Bakuríu).
gerrit
@gerrit. Stoję poprawiony i nauczyłem się nowej rzeczy. Usunąłem mój oryginalny komentarz, aby uniknąć wywołania szału zbierania odznak.
Mad Physicist
Innym podejściem, którego możesz użyć, jest po prostu sprawdzenie, czy mianownik jest równy 0 przed dzieleniem, co pozwala uniknąć narzutu związanego z manipulowaniem systemem ostrzegawczym Numpy. (Chociaż prawdopodobnie oznaczałoby to, że musisz rozszerzyć schludne rozumienie listy na pętlę sprawdzającą, czy którykolwiek z mianowników jest zerowy.)
Oliver

Odpowiedzi:

197

Wygląda na to, że Twoja konfiguracja używa printopcji dla 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])

Oznacza to, że ostrzeżenie, które widzisz, nie jest prawdziwym ostrzeżeniem, ale jest to tylko kilka znaków drukowanych stdout(zobacz dokumentację seterr). Jeśli chcesz go złapać, możesz:

  1. Użycie, numpy.seterr(all='raise')które bezpośrednio spowoduje zgłoszenie wyjątku. To jednak zmienia zachowanie wszystkich operacji, więc jest to dość duża zmiana w zachowaniu.
  2. Użyj numpy.seterr(all='warn'), który przekształci wydrukowane ostrzeżenie w prawdziwe ostrzeżenie, a będziesz mógł użyć powyższego rozwiązania, aby zlokalizować tę zmianę w zachowaniu.

Gdy otrzymasz ostrzeżenie, możesz użyć warningsmodułu, aby kontrolować, jak ostrzeżenia powinny być traktowane:

>>> import warnings
>>> 
>>> warnings.filterwarnings('error')
>>> 
>>> try:
...     warnings.warn(Warning())
... except Warning:
...     print 'Warning was raised as an exception!'
... 
Warning was raised as an exception!

Przeczytaj uważnie dokumentację, filterwarningsponieważ pozwala filtrować tylko żądane ostrzeżenie i ma inne opcje. Zastanowiłbym się również nad tym, catch_warningsktóry jest menedżerem kontekstu, który automatycznie resetuje oryginalną filterwarningsfunkcję:

>>> 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
źródło
Myślę, że to początek. Ale to tak naprawdę nie rozwiązuje mojego problemu. Jeśli dodam warnings.warn (Warning ())) w moim kodzie w bloku try, złapie ostrzeżenie. Z jakiegoś powodu nie łapie ostrzeżenia o dzieleniu przez zero. Oto dokładny komunikat ostrzegawczy: Ostrzeżenie: dzielenie przez zero napotkano w int_scalars
John K.
@JohnK. Powinieneś edytować swoje pytanie i dodać dokładny wynik, w przeciwnym razie nie będziemy mogli powiedzieć, co jest nie tak. To może być możliwe, że numpy definiuje to ostrzeżenie klasy i gdzieś trzeba Discovere w który podpakiet aby móc go złapać. Nieważne, odkryłem, że powinieneś użyć RuntimeWarning. Zaktualizowałem odpowiedź.
Bakuriu,
Jesteś pewny? Zmieniłem mój kod, aby używać z wyjątkiem RuntimeWarning :. Nadal nie działa = /
John K.
@JohnK. W dokumentacji stwierdza się, że RuntimeWarningpodniesiono a. Problem może polegać na tym, że twoja konfiguracja numpy używa printopcji, która po prostu drukuje ostrzeżenie, ale nie jest to prawdziwe ostrzeżenie obsługiwane przez warningsmoduł ... Jeśli tak jest, możesz spróbować użyć numpy.seterr(all='warn')i spróbować ponownie.
Bakuriu,
3
W mojej wersji numpynie możesz używać numpy.seterr(all='error'), errormusi być raise.
detly
41

Aby dodać trochę do odpowiedzi @ Bakuriu:

Jeśli już wiesz, gdzie prawdopodobnie wystąpi ostrzeżenie, często lepiej jest użyć numpy.errstatemenedżera kontekstu, zamiast numpy.seterrtraktować wszystkie kolejne ostrzeżenia tego samego typu tak samo niezależnie od tego, gdzie występują w kodzie:

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

Edytować:

W moim oryginalnym przykładzie miałem a = np.r_[0], ale najwyraźniej nastąpiła zmiana w zachowaniu numpy'ego, tak że dzielenie przez zero jest traktowane inaczej w przypadkach, gdy licznik jest zerowy. Na przykład w 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

Odpowiednie komunikaty ostrzegawcze są również różne: 1. / 0.jest rejestrowany jako RuntimeWarning: divide by zero encountered in true_divide, a 0. / 0.jako RuntimeWarning: invalid value encountered in true_divide. Nie jestem pewien, dlaczego dokładnie ta zmiana została wprowadzona, ale podejrzewam, że ma to związek z faktem, że wynik 0. / 0.nie jest reprezentowalny jako liczba (numpy zwraca w tym przypadku NaN), podczas gdy 1. / 0.i -1. / 0.zwracają odpowiednio + Inf i -Inf , zgodnie ze standardem IEE 754.

Jeśli chcesz wyłapać oba typy błędów, zawsze możesz przejść np.errstate(divide='raise', invalid='raise'), lub all='raise'jeśli chcesz zgłosić wyjątek dla dowolnego rodzaju błędu zmiennoprzecinkowego.

ali_m
źródło
W szczególności podnosi się FloatingPointError, nie ZeroDivisionError.
gerrit
To nie działa Python 3.6.3z numpy==1.16.3. Czy mógłbyś to zaktualizować?
anilbey
1
@anilbey Najwyraźniej nastąpiła zmiana w zachowaniu numpy, co oznacza, że ​​dzielenie przez zero jest teraz obsługiwane inaczej, w zależności od tego, czy licznik również wynosi (wszystkie) zero.
ali_m
27

Aby rozwinąć powyższą odpowiedź @ Bakuriu, stwierdziłem, że pozwala mi to złapać ostrzeżenie w czasie wykonywania w podobny sposób, w jaki złapałbym ostrzeżenie o błędzie, ładnie drukując ostrzeżenie:

import warnings

with warnings.catch_warnings():
    warnings.filterwarnings('error')
    try:
        answer = 1 / 0
    except Warning as e:
        print('error found:', e)

Prawdopodobnie będziesz mógł pobawić się rozmieszczeniem ostrzeżeń.catch_warnings () w zależności od tego, jak duży parasol chcesz zarzucić z wyłapywaniem błędów w ten sposób.

ntk4
źródło
3
odpowiedź =
1/0
8

Usuń ostrzeżenia.filterwarnings i dodaj:

numpy.seterr(all='raise')
Shital Shah
źródło