Warum kann ich diese Python-Ausnahme nicht abfangen?

9

Ich habe ein paar etcd-Module für SaltStack geschrieben und bin in dieses seltsame Problem geraten, wo es mich irgendwie daran hindert, eine Ausnahme einzufangen, und ich bin daran interessiert, wie es das macht. Es scheint speziell um urllib3 zentriert.

Ein kleines Skript (nicht Salz):

%Vor%

Und wenn wir es ausführen:

%Vor%

Ok, fangen wir den Fehler:

%Vor%

Führen Sie es aus:

%Vor%

Sieht gut aus - es funktioniert alles Python. Also, was ist das Problem? Ich habe dies in der Salz-etcd-Modul:

%Vor%

Und wenn wir das ausführen:

%Vor%

Hrm, das ist komisch. etcd's gelesen sollte etcd.EtcdConnectionFailed zurückgegeben haben. Sehen wir uns das genauer an. Unser Modul ist jetzt das:

%Vor%

Und wir bekommen:

%Vor%

Ok, also wissen wir, dass wir das Ding fangen können. Und wir wissen jetzt, dass es einen ReadTimeoutError geworfen hat, also fangen wir es. Die neueste Version unseres Moduls:

%Vor%

Und unser Test ..

%Vor%

Äh, warte, was? Warum haben wir das nicht verstanden? Ausnahmen funktionieren, richtig ..?

Wie wäre es, wenn wir versuchen, die Basisklasse von urllib3 abzufangen.

%Vor%

Hoffen und beten ..

%Vor%

BLAST YE! Ok, lass uns eine andere Methode ausprobieren, die eine andere etcd Exception zurückgibt. Unser Modul sieht jetzt so aus:

%Vor%

Und unser Lauf:

%Vor%

Als letzten Test habe ich dieses Modul erstellt, das ich entweder aus straight python oder als Salzmodul ausführen kann.

%Vor%

Durch Python:

%Vor%

Durch Salz:

%Vor%

So können wir Ausnahmen von der geworfenen etcd abfangen. Aber während wir in der Lage sind, den urllib3 ReadTimeoutError zu fangen, wenn wir python-etcd nach dem anderen ausführen, scheint nichts in der Lage zu sein, diese urllib3-Ausnahme zu fangen, außer einer generellen 'Exception'-Klausel. p>

Ich kann das tun, aber ich bin wirklich neugierig, was das Lecksalz macht, das macht es so, dass eine Ausnahme nicht mehr möglich ist. Ich habe das noch nie zuvor bei der Arbeit mit Python gesehen, also wäre ich neugierig, wie es passiert und wie ich es umgehen kann.

Bearbeiten:

So konnte ich es endlich fangen.

%Vor%

Und wenn ausgeführt:

%Vor%

Es ergibt jedoch immer noch keinen Sinn. Soweit ich von Ausnahmen weiß, sollte die Rückgabe "1" sein. Warum sollte ich den Namen der Ausnahme direkt importieren müssen, anstatt nur den vollständigen Klassennamen zu verwenden?

MEHR EDITS!

Wenn man also den Vergleich zwischen den beiden Klassen hinzufügt, ergibt sich 'Falsch' - was offensichtlich ist, weil die except-Klausel nicht funktioniert hat, so dass diese nicht identisch sein können.

Ich habe folgendes zum Skript hinzugefügt, direkt bevor ich c.read () anrufe.

%Vor%

Und jetzt bekomme ich das im Log:

%Vor%

Das scheint also der Grund dafür zu sein, dass er so gefangen wird, wie er ist. Dies ist auch reproduzierbar, indem Sie einfach die etcd- und Requests-Bibliothek herunterladen und so etwas tun:

%Vor%

Am Ende wird die "richtige" Ausnahme ausgelöst - etcd.EtcdConnectionFailed. Entkommen Sie jedoch von "Anfragen" und Sie werden mit "urllib3.exceptions.ReadTimeoutError" enden, weil "etcd" die Ausnahme jetzt nicht mehr abfängt.

So scheint es, dass beim Importieren von Anforderungen die urllib3-Ausnahmen neu geschrieben werden und jedes andere Modul, das versucht, diese zu fangen, fehlschlägt. Es scheint auch, dass neuere Versionen von Anforderungen dieses Problem nicht haben.

    
sjmh 04.11.2015, 07:36
quelle

1 Antwort

3

Meine Antwort unten ist etwas spekulativ, weil ich es mit diesen exakten Bibliotheken in der Praxis nicht beweisen kann (zu Beginn kann ich Ihren Fehler nicht reproduzieren, da es auch von Bibliothekenversionen und deren Installation abhängt), zeigt aber trotzdem einen der möglichen Wege dieses Geschehens:

Das allerletzte Beispiel gibt einen guten Hinweis: Der Punkt ist in der Tat, dass zu verschiedenen Zeitpunkten in der Zeit der Programmausführung der Name urllib3.exceptions.ReadTimeoutError sich auf verschiedene Klassen beziehen kann. ReadTimeoutError ist, genau wie jedes andere Modul in Python, einfach ein Name im Namensraum urllib3.exceptions , und kann neu zugewiesen werden (aber das bedeutet nicht, dass es eine gute Idee ist) so).

Wenn wir auf diesen Namen mit seinem vollqualifizierten "Pfad" verweisen, können wir uns auf den tatsächlichen Zustand des Namens beziehen, wenn wir darauf verweisen. Wenn wir es zum ersten Mal importieren wie from urllib3.exceptions import ReadTimeoutError - bringt es den Namen ReadTimeoutError in den Namespace, der den Import durchführt, und dieser Name ist zum Zeitpunkt des Imports urllib3.exceptions.ReadTimeoutError gebunden >. Wenn nun ein anderer Code den Wert für urllib3.exceptions.ReadTimeoutError später neu zuweist, sind die beiden Werte (der "aktuelle" / "neueste" Wert und der zuvor importierte) tatsächlich unterschiedlich - also könnten Sie theoretisch zwei verschiedene Klassen haben. Nun, welche Ausnahmeklasse tatsächlich ausgelöst wird - das hängt davon ab, wie der Code, der den Fehler auslöst, ihn benutzt: Wenn sie zuvor ReadTimeoutError in ihren Namespace importiert haben, wird dieser (das "Original") ausgelöst.

Um zu überprüfen, ob dies der Fall ist, können Sie dem except ReadTimeoutError -Block Folgendes hinzufügen:

%Vor%

Wenn dies False ausgibt, beweist es, dass die beiden "Referenzen" zu dem Zeitpunkt, zu dem die Ausnahme ausgelöst wird, tatsächlich auf andere Klassen verweisen.

Ein vereinfachtes Beispiel einer schlechten Implementierung, die ein ähnliches Ergebnis liefern kann:

Datei api.py (richtig entworfen und existiert glücklich von selbst):

%Vor%

Datei apibreaker.py (die Schuldige):

%Vor%

Datei apiuser.py :

%Vor%

Wenn ausgeführt:

%Vor%

Wenn Sie die Zeile import apibreaker - deutlich entfernen, dann werden alle wieder an ihren Platz gebracht, wie es sein sollte.

Dies ist ein sehr vereinfachtes Beispiel, aber illustrativ genug, um zu zeigen, dass, wenn eine Klasse in einem Modul definiert ist - neu erzeugter Typ (Objekt, das eine neue Klasse darstellt) unter dem deklarierten Klassennamen dem Namensraum des Moduls "hinzugefügt" wird. Wie bei jeder anderen Variable - ihr Wert kann technisch verändert werden. Gleiches passiert mit Funktionen.

    
Timur 07.11.2015, 21:00
quelle

Tags und Links