Wie fange ich eine numpy Warnung ab, als wäre es eine Ausnahme (nicht nur zum Testen)?

172

Ich muss ein Lagrange-Polynom in Python für ein Projekt erstellen, das ich gerade mache. Ich mache einen baryzentrischen Stil, um zu vermeiden, dass eine explizite for-Schleife verwendet wird, im Gegensatz zu einem Newton-Stil mit geteiltem Unterschied. Das Problem, das ich habe, ist, dass ich eine Division durch Null abfangen muss, aber Python (oder vielleicht Numpy) macht es nur zu einer Warnung anstelle einer normalen Ausnahme.

Ich muss also wissen, wie ich diese Warnung abfangen soll, als wäre es eine Ausnahme. Die diesbezüglichen Fragen, die ich auf dieser Website gefunden habe, wurden nicht so beantwortet, wie ich es brauchte. Hier ist mein Code:

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. 

Wenn dieser Code ausgeführt wird, erhalte ich folgende Ausgabe:

Warning: divide by zero encountered in int_scalars

Das ist die Warnung, die ich fangen möchte. Es sollte innerhalb des Listenverständnisses auftreten.

John K.
quelle
2
Bist du dir ziemlich sicher, dass es so ist Warning: ...? Versuche Dinge wie np.array([1])/0ich bekomme RuntimeWarning: ...als Ausgabe.
Bakuriu
1
@ MadPhysicist Kein Duplikat; NumPy verfügt über eine eigene interne Warnarchitektur auf Pythons, die spezifisch gesteuert werden kann (siehe Antwort von Bakuríu).
Gerrit
@gerrit. Ich stehe korrigiert da und habe etwas Neues gelernt. Ich habe meinen ursprünglichen Kommentar gelöscht, um nicht den Wahnsinn der Abzeichensammlung auszulösen.
Mad Physicist
Ein anderer Ansatz, den Sie verwenden können, besteht darin, vor der Division einfach zu überprüfen, ob der Nenner 0 ist, wodurch der Aufwand vermieden wird, mit dem Warnsystem von numpy herumzuspielen. (Obwohl dies wahrscheinlich bedeuten würde, dass Sie das ordentliche Listenverständnis in eine Schleife erweitern müssen, um zu überprüfen, ob einer der Nenner Null ist.)
Oliver

Antworten:

196

Es scheint, dass Ihre Konfiguration die printOption verwendet für 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])

Dies bedeutet, dass die Warnung, die Sie sehen, keine echte Warnung ist, sondern nur einige Zeichen, auf die gedruckt wird stdout(siehe Dokumentation für seterr). Wenn Sie es fangen wollen, können Sie:

  1. Verwenden Sie numpy.seterr(all='raise')diese Option, um die Ausnahme direkt auszulösen. Dies ändert jedoch das Verhalten aller Vorgänge, sodass sich das Verhalten ziemlich stark ändert.
  2. Verwenden Sie numpy.seterr(all='warn')diese Option, um die gedruckte Warnung in eine echte Warnung umzuwandeln, und Sie können die oben beschriebene Lösung verwenden, um diese Verhaltensänderung zu lokalisieren.

Sobald Sie tatsächlich eine Warnung erhalten haben, können Sie mit dem warningsModul steuern, wie die Warnungen behandelt werden sollen:

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

Lesen Sie die Dokumentation sorgfältig durch, filterwarningsda Sie nur die gewünschte Warnung filtern können und andere Optionen haben. Ich würde auch überlegen, catch_warningswelcher Kontextmanager die ursprüngliche filterwarningsFunktion automatisch zurücksetzt :

>>> 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
quelle
Ich denke das ist ein Anfang. Aber es behebt mein Problem nicht wirklich. Wenn ich in meinem Code im try-Block warnings.warn (Warning ())) hinzufüge, wird die Warnung abgefangen. Aus irgendeinem Grund wird die Warnung zum Teilen durch Null nicht erfasst. Hier ist die genaue Warnmeldung: Warnung: Teilen durch Null in int_scalars
John K.
@ JohnK. Sie sollten Ihre Frage bearbeiten und die genaue Ausgabe hinzufügen, da wir sonst nicht sagen können, was falsch ist. Es könnte möglich sein , dass numpy diese Warnung Klasse irgendwo definiert und Sie haben in der subpackage Discovere es fangen zu können. Egal, ich habe festgestellt, dass Sie verwenden sollten RuntimeWarning. Die Antwort wurde aktualisiert.
Bakuriu
Bist du sicher? Ich habe meinen Code geändert, um ihn außer RuntimeWarning: zu verwenden. Es funktioniert immer noch nicht = /
John K.
@ JohnK. In der Dokumentation heißt es, dass a RuntimeWarningausgelöst wird. Das Problem könnte sein, dass Ihre Numpy-Konfiguration die printOption verwendet, die einfach die Warnung druckt, aber keine echte Warnung ist, die vom warningsModul verarbeitet wird. In diesem Fall können Sie versuchen, numpy.seterr(all='warn')sie erneut zu verwenden.
Bakuriu
3
In meiner Version von numpykann man nicht verwenden numpy.seterr(all='error'), errormuss sein raise.
Detly
41

Um ein wenig zu @ Bakurius Antwort hinzuzufügen:

Wenn Sie bereits wissen, wo die Warnung wahrscheinlich auftritt, ist es häufig sauberer, den numpy.errstateKontextmanager zu verwenden , als numpy.seterralle nachfolgenden Warnungen desselben Typs gleich zu behandeln, unabhängig davon, wo sie in Ihrem Code auftreten:

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

Bearbeiten:

In meinem ursprünglichen Beispiel hatte ich a = np.r_[0], aber anscheinend gab es eine Änderung im Verhalten von numpy, so dass die Division durch Null in Fällen, in denen der Zähler nur Null ist, anders gehandhabt wird. Zum Beispiel in 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

Die entsprechenden Warnmeldungen sind ebenfalls unterschiedlich: 1. / 0.wird als protokolliert RuntimeWarning: divide by zero encountered in true_divide, während 0. / 0.als protokolliert wird RuntimeWarning: invalid value encountered in true_divide. Ich bin nicht sicher, warum genau diese Änderung vorgenommen wurde, aber ich vermute, dass dies damit zusammenhängt, dass das Ergebnis von 0. / 0.nicht als Zahl dargestellt werden kann (numpy gibt in diesem Fall eine NaN zurück), während 1. / 0.und -1. / 0.+ Inf bzw. -Inf zurückgibt gemäß dem IEE 754-Standard.

Wenn Sie beide Fehlertypen abfangen möchten, können Sie diese jederzeit übergeben np.errstate(divide='raise', invalid='raise')oder all='raise'eine Ausnahme für Gleitkommafehler jeglicher Art auslösen.

ali_m
quelle
Insbesondere erhöht es FloatingPointErrornicht ZeroDivisionError.
Gerrit
Dies funktioniert nicht Python 3.6.3mit numpy==1.16.3. Könnten Sie es bitte aktualisieren?
Anilbey
1
@anilbey Anscheinend hat sich das Verhalten von numpy geändert, was bedeutet, dass die Division durch Null jetzt unterschiedlich behandelt wird, je nachdem, ob der Zähler auch (alle) Null ist.
Ali_m
27

Um auf die Antwort von @ Bakuriu oben einzugehen, habe ich festgestellt, dass ich auf diese Weise eine Laufzeitwarnung abfangen kann, ähnlich wie ich eine Fehlerwarnung abfangen würde, indem ich die Warnung schön ausdrucke:

import warnings

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

Sie werden wahrscheinlich in der Lage sein, mit dem Platzieren der Platzierung von warnings.catch_warnings () herumzuspielen, je nachdem, wie groß ein Regenschirm sein soll, den Sie auf diese Weise mit abfangenden Fehlern besetzen möchten.

ntk4
quelle
3
Antwort =
1/0
8

Entfernen Sie warnings.filterwarnings und fügen Sie hinzu:

numpy.seterr(all='raise')
Shital Shah
quelle