Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

Zusammenfassung

„Für Klarheit in neuem Code wird die Form raise 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

  1. Im Code
    try:
        raise MyError, raised
    except MyError, caught:
        pass
    

    die syntaktische Parallele zwischen der raise- und except-Anweisung legt nahe, dass ausgelöst und gefangen sich auf dasselbe Objekt beziehen. Bei String-Ausnahmen ist dies tatsächlich der Fall, bei Instanz-Ausnahmen jedoch nicht.

  2. 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 raise geschieht, 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.)

  3. 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.

  4. 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

  1. Erfordern, dass Ausnahmetypen Unterklassen von Exception sind und implizit instanziieren, wenn und nur wenn
    issubclass(firstarg, Exception)
    
  2. 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


Quelle: https://github.com/python/peps/blob/main/peps/pep-0317.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT