PEP 317 – Implizite Ausnahmeinstanziierung abschaffen
- Autor:
- Steven Taschuk <staschuk at telusplanet.net>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 06-Mai-2003
- Python-Version:
- 2.4
- Post-History:
- 09-Jun-2003
Zusammenfassung
„Für Klarheit in neuem Code wird die Formraise Klasse(Argument, ...)empfohlen (d.h. ein expliziter Aufruf des Konstruktors).“—Guido van Rossum, 1997 [1]
Dieser PEP schlägt die formelle Verwarnung und die endgültige Abschaffung von Formen der raise-Anweisung vor, die eine Ausnahme implizit instanziieren. Zum Beispiel werden Anweisungen wie
raise HullBreachError
raise KitchenError, 'all out of baked beans'
müssen nach diesem Vorschlag durch ihre Synonyme ersetzt werden
raise HullBreachError()
raise KitchenError('all out of baked beans')
Beachten Sie, dass die letzteren Anweisungen bereits legal sind und dieser PEP ihre Bedeutung nicht ändert.
Die Abschaffung dieser Formen von raise macht die Verwendung von String-Ausnahmen unmöglich; dementsprechend schlägt dieser PEP auch die formelle Verwarnung und die endgültige Abschaffung von String-Ausnahmen vor.
Die Annahme dieses Vorschlags bricht die Rückwärtskompatibilität. Gemäß dem vorgeschlagenen Zeitplan wird Python 2.4 Warnungen für die Verwendung von raise einführen, die schließlich ungültig werden, und Python 3.0 wird sie vollständig abschaffen. (Es wird angenommen, dass diese Übergangsperiode – von 2.4 bis 3.0 – mindestens ein Jahr lang sein wird, um den Richtlinien von PEP 5 zu entsprechen.)
Motivation
String-Ausnahmen
Es wird angenommen, dass die Entfernung von String-Ausnahmen unumstritten sein wird, da sie seit mindestens Python 1.5 beabsichtigt ist, als die Standard-Ausnahmetypen in Klassen geändert wurden [1].
Zur Aufzeichnung: String-Ausnahmen sollten entfernt werden, da die Existenz zweier Arten von Ausnahmen die Sprache ohne Entschädigung kompliziert. Instanz-Ausnahmen sind überlegen, weil zum Beispiel
- die Klasse-Instanz-Beziehung den Zusammenhang zwischen Ausnahmetyp und -wert natürlicher ausdrückt,
- sie können naturgemäß mit Hilfe von Superklasse-Unterklasse-Beziehungen organisiert werden, und
- sie können Fehlerberichtsverhalten (zum Beispiel) kapseln.
Implizite Instanziierung
Guidos Essay von 1997 [1] über die Umwandlung der Standard-Ausnahmen in Klassen macht deutlich, warum raise implizit instanziieren kann
„Die raise-Anweisung wurde erweitert, um das Auslösen einer Klassen-Ausnahme ohne explizite Instanziierung zu ermöglichen. Die folgenden Formen, die „Kompatibilitätsformen“ der raise-Anweisung genannt werden […] Die Motivation für die Einführung der Kompatibilitätsformen war, die Rückwärtskompatibilität mit altem Code zu ermöglichen, der eine Standard-Ausnahme auslöste.“
Zum Beispiel war es gewünscht, dass Code vor 1.5, der die String-Ausnahme-Syntax verwendete, wie z.B.
raise TypeError, 'not an int'
sowohl auf Python-Versionen, in denen TypeError ein String war, als auch auf Versionen, in denen es eine Klasse war, funktionieren würde.
Wenn keine solche Überlegung vorliegt – das heißt, wenn der gewünschte Ausnahmetyp in keiner Softwareversion, die der Code unterstützen muss, ein String ist –, gibt es keinen guten Grund, implizit zu instanziieren, und es ist klarer, es nicht zu tun. Zum Beispiel
- Im Code
try: raise MyError, raised except MyError, caught: pass
die syntaktische Parallele zwischen der
raise- undexcept-Anweisung legt nahe, dassausgelöstundgefangensich auf dasselbe Objekt beziehen. Bei String-Ausnahmen ist dies tatsächlich der Fall, bei Instanz-Ausnahmen jedoch nicht. - Wenn die Instanziierung implizit ist, ist nicht offensichtlich, wann sie auftritt, zum Beispiel, ob sie auftritt, wenn die Ausnahme ausgelöst oder wenn sie gefangen wird. Da sie tatsächlich bei der
raisegeschieht, sollte der Code dies sagen.(Beachten Sie, dass auf der Ebene der C-API eine Ausnahme „ausgelöst“ und „gefangen“ werden kann, ohne instanziiert zu werden; dies wird als Optimierung verwendet, z. B. von
PyIter_Next. Aber in Python ist keine solche Optimierung verfügbar oder sollte verfügbar sein.) - Eine implizit instanziierende
raise-Anweisung ohne Argumente, wie z. B.raise MyError
tut einfach nicht, was sie sagt: sie löst nicht das genannte Objekt aus.
- Die Gleichheit von
raise MyError raise MyError()
vermischt Klassen und Instanzen und schafft eine mögliche Verwirrungsquelle für Anfänger. (Außerdem ist nicht klar, ob der Interpreter zwischen einer neuen Klassenart und einer Instanz einer solchen Klasse unterscheiden könnte, so dass die implizite Instanziierung ein Hindernis für jeden zukünftigen Plan sein könnte, Ausnahmen zu neuen Klassenobjekten zu machen.)
Kurz gesagt, die implizite Instanziierung hat keine Vorteile außer der Rückwärtskompatibilität, und daher sollte sie zusammen mit dem, wofür sie zur Gewährleistung der Kompatibilität existiert, nämlich String-Ausnahmen, schrittweise abgebaut werden.
Spezifikation
Die Syntax von raise_stmt [3] wird geändert von
raise_stmt ::= "raise" [expression ["," expression ["," expression]]]
zu
raise_stmt ::= "raise" [expression ["," expression]]
Wenn keine Ausdrücke vorhanden sind, verhält sich die raise-Anweisung wie bisher: sie löst die letzte in Geltungsbereich aktive Ausnahme erneut aus, und wenn in Geltungsbereich keine Ausnahme aktiv war, wird eine TypeError ausgelöst, die angibt, dass dies das Problem ist.
Andernfalls wird der erste Ausdruck ausgewertet, was das ausgelöste Objekt ergibt. Dann wird der zweite Ausdruck, falls vorhanden, ausgewertet, was das ersetze Traceback ergibt. Wenn kein zweiter Ausdruck vorhanden ist, ist der ersetzte Traceback None.
Das ausgelöste Objekt muss eine Instanz sein. Die Klasse der Instanz ist der Ausnahmetyp und die Instanz selbst ist der Ausnahme-Wert. Wenn das ausgelöste Objekt keine Instanz ist – zum Beispiel, wenn es eine Klasse oder ein String ist –, wird eine TypeError ausgelöst.
Wenn der ersetzte Traceback nicht None ist, muss er ein Traceback-Objekt sein, und er wird anstelle des aktuellen Ortes, an dem die Ausnahme aufgetreten ist, eingesetzt. Wenn er weder ein Traceback-Objekt noch None ist, wird eine TypeError ausgelöst.
Abwärtskompatibilität
Migrationsplan
Future Statement
Unter der PEP 236 `future`-Anweisung
from __future__ import raise_with_two_args
werden die Syntax und Semantik der raise-Anweisung wie oben beschrieben sein. Dieses zukünftige Feature wird in Python 2.4 erscheinen; seine Wirkung wird in Python 3.0 Standard sein.
Wie die folgenden Beispiele zeigen, ist diese `future`-Anweisung nur für Code erforderlich, der das Argument für den ersetzten Traceback von raise verwendet; einfache Ausnahme-Auslösungen erfordern sie nicht.
Warnungen
Drei neue Warnungen, alle von der Kategorie DeprecationWarning, werden ausgegeben, um auf Verwendungen von raise hinzuweisen, die im Rahmen der vorgeschlagenen Änderungen ungültig werden.
Die erste Warnung wird ausgegeben, wenn eine raise-Anweisung ausgeführt wird, bei der der erste Ausdruck zu einem String ausgewertet wird. Die Meldung für diese Warnung ist
raising strings will be impossible in the future
Die zweite Warnung wird ausgegeben, wenn eine raise-Anweisung ausgeführt wird, bei der der erste Ausdruck zu einer Klasse ausgewertet wird. Die Meldung für diese Warnung ist
raising classes will be impossible in the future
Die dritte Warnung wird ausgegeben, wenn eine raise-Anweisung mit drei Ausdrücken kompiliert wird. (Nicht, beachten Sie, wenn sie ausgeführt wird; dies ist wichtig, da die SyntaxError, die diese Warnung ankündigt, zur Kompilierzeit auftritt.) Die Meldung für diese Warnung ist
raising with three arguments will be impossible in the future
Diese Warnungen sollen in Python 2.4 erscheinen und in Python 3.0 verschwinden, wenn die Bedingungen, die sie verursachen, einfach Fehler sind.
Beispiele
Code, der implizite Instanziierung verwendet
Code wie
class MyError(Exception):
pass
raise MyError, 'spam'
wird eine Warnung ausgeben, wenn die raise-Anweisung ausgeführt wird. Die raise-Anweisung sollte geändert werden, um explizit zu instanziieren
raise MyError('spam')
Code, der String-Ausnahmen verwendet
Code wie
MyError = 'spam'
raise MyError, 'eggs'
wird eine Warnung ausgeben, wenn die raise-Anweisung ausgeführt wird. Der Ausnahmetyp sollte in eine Klasse geändert werden
class MyError(Exception):
pass
und, wie im vorherigen Beispiel, sollte die raise-Anweisung geändert werden, um explizit zu instanziieren
raise MyError('eggs')
Code, der ein Traceback-Objekt bereitstellt
Code wie
raise MyError, 'spam', mytraceback
wird eine Warnung ausgeben, wenn sie kompiliert wird. Die Anweisung sollte geändert werden zu
raise MyError('spam'), mytraceback
und die `future`-Anweisung
from __future__ import raise_with_two_args
sollte am Anfang des Moduls hinzugefügt werden. Beachten Sie, dass das Hinzufügen dieser `future`-Anweisung auch die anderen beiden Warnungen in Fehler umwandelt, sodass die in den vorherigen Beispielen beschriebenen Änderungen ebenfalls vorgenommen werden müssen.
Der Sonderfall
raise sys.exc_type, sys.exc_info, sys.exc_traceback
(der dazu bestimmt ist, eine vorherige Ausnahme erneut auszulösen) sollte einfach geändert werden in
raise
Ein Scheitern des Plans
Es kann vorkommen, dass eine raise-Anweisung, die einen String auslöst oder implizit instanziiert, während der Einführungsphase dieses PEPs nicht in Produktion oder beim Testen ausgeführt wird. In diesem Fall wird keine Warnung ausgegeben, sondern stattdessen eines Tages in Python 3.0 oder einer späteren Version plötzlich fehlschlagen. (Der Fehler besteht darin, dass die falsche Ausnahme ausgelöst wird, nämlich eine TypeError, die über die Argumente für raise beschwert, anstatt die beabsichtigte Ausnahme.)
Solche Fälle können durch Verlängerung der Einführungsphase seltener gemacht werden; sie können nicht unmöglich gemacht werden, ohne dass zur Kompilierzeit für jede raise-Anweisung eine Warnung ausgegeben wird.
Ablehnung
Wenn dieser PEP angenommen worden wäre, müsste fast der gesamte bestehende Python-Code überprüft und wahrscheinlich überarbeitet werden; selbst wenn alle obigen Argumente für die explizite Instanziierung akzeptiert werden, ist die Verbesserung der Klarheit zu gering, um die Kosten dieser Überarbeitung und das Risiko neuer Fehler, die dadurch entstehen, zu rechtfertigen.
Dieser Vorschlag wurde daher abgelehnt [6].
Beachten Sie, dass String-Ausnahmen unabhängig von diesem Vorschlag zur Entfernung vorgesehen sind; abgelehnt wird die Entfernung der impliziten Ausnahmeinstanziierung.
Zusammenfassung der Diskussion
Eine kleine Minderheit der Befragten war für den Vorschlag, aber die vorherrschende Antwort war, dass eine solche Migration unverhältnismäßig teuer wäre im Vergleich zum vermeintlichen Nutzen. Wie oben erwähnt, ist dieser Punkt allein ausreichend, um den PEP abzulehnen.
Neue Ausnahme-Arten
Die implizite Instanziierung könnte mit zukünftigen Plänen zur Verwendung von Instanzen von neuen Klassen als Ausnahmen in Konflikt geraten. Um zu entscheiden, ob implizit instanziiert werden soll, muss die raise-Maschinerie feststellen, ob das erste Argument eine Klasse oder eine Instanz ist – aber bei neuen Klassen gibt es keine klare und starke Unterscheidung.
Unter diesem Vorschlag würde das Problem vermieden, da die Ausnahme bereits instanziiert worden wäre. Es gibt jedoch zwei plausible alternative Lösungen
- Erfordern, dass Ausnahmetypen Unterklassen von
Exceptionsind und implizit instanziieren, wenn und nur wennissubclass(firstarg, Exception)
- Implizit instanziieren, wenn und nur wenn
isinstance(firstarg, type)
Somit ist die vollständige Abschaffung der impliziten Instanziierung nicht notwendig, um dieses Problem zu lösen.
Hässlichkeit der expliziten Instanziierung
Einige Befragte empfanden die explizit instanziierende Syntax als unschöner, insbesondere in Fällen, in denen keine Argumente an den Ausnahme-Konstruktor übergeben werden
raise TypeError()
Das Problem ist besonders akut, wenn die Ausnahme-Instanz selbst nicht von Interesse ist, d.h., wenn der einzige relevante Punkt der Ausnahmetyp ist
try:
# ... deeply nested search loop ...
raise Found
except Found:
# ...
In solchen Fällen kann die Symmetrie zwischen raise und except die Absicht des Codes besser ausdrücken.
Guido meinte, dass die implizit instanziierende Syntax „ein bisschen schöner“ sei, selbst für Fälle mit einem einzigen Argument, da sie weniger Satzzeichen aufweist.
Performance-Strafe für Warnungen
Die Erfahrung mit der Verwarnung von apply() zeigt, dass die Verwendung des Warnrahmens eine signifikante Leistungsbeeinträchtigung mit sich bringen kann.
Code, der explizit instanziiert, wäre nicht betroffen, da die zur Bestimmung der Notwendigkeit einer Warnung erforderlichen Laufzeitprüfungen genau die sind, die benötigt werden, um festzustellen, ob überhaupt implizit instanziiert werden soll. Das heißt, solche Anweisungen verursachen bereits die Kosten dieser Prüfungen.
Code, der implizit instanziiert, würde hohe Kosten verursachen: Timing-Tests zeigen, dass das Ausgeben einer Warnung (ob unterdrückt oder nicht) etwa fünfmal länger dauert als das einfache Instanziieren, Auslösen und Abfangen einer Ausnahme.
Diese Strafe wird dadurch gemildert, dass raise-Anweisungen selten auf leistungsabhängigen Ausführungspfaden liegen.
Traceback-Argument
Nach dem jetzigen Vorschlag wäre es unmöglich, das Traceback-Argument von raise bequem mit allen Python 2.x-Versionen zu verwenden.
Für die Kompatibilität mit Versionen < 2.4 muss die Drei-Argument-Form verwendet werden; diese Form würde jedoch mit Versionen >= 2.4 Warnungen erzeugen. Diese Warnungen könnten unterdrückt werden, aber das ist umständlich, da die relevante Art der Warnung zur Kompilierzeit ausgegeben wird.
Wenn dieser PEP noch in Erwägung gezogen würde, würde diesem Einwand durch Verlängerung der Einführungsphase begegnet. Zum Beispiel könnten Warnungen zuerst in 3.0 ausgegeben und erst in einer späteren Version zu Fehlern werden.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0317.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT