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

Python Enhancement Proposals

PEP 230 – Warnungsframework

Autor:
Guido van Rossum <guido at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
28. Nov. 2000
Python-Version:
2.1
Post-History:
05. Nov. 2000

Inhaltsverzeichnis

Zusammenfassung

Diese PEP schlägt eine C- und Python-API sowie Kommandozeilen-Flags vor, um Warnmeldungen auszugeben und zu steuern, was mit ihnen geschieht. Dies basiert größtenteils auf GvRs Vorschlag, der am 05. Nov. 2000 an python-dev gesendet wurde, wobei einige Ideen (wie die Verwendung von Klassen zur Kategorisierung von Warnungen) aus Paul Prescods Gegenentwurf vom selben Datum übernommen wurden. Außerdem hat ein Versuch, den Vorschlag zu implementieren, zu mehreren kleinen Anpassungen geführt.

Motivation

Da Python 3000 vor der Tür steht, ist es notwendig, zusätzlich zu Fehlern auch Warnungen über die Verwendung veralteter oder abgelegter Funktionen auszugeben. Es gibt auch viele andere Gründe, Warnungen ausgeben zu können, sowohl aus C- als auch aus Python-Code, sowohl zur Kompilierungs- als auch zur Laufzeit.

Warnungen sind nicht fatal, und daher ist es möglich, dass ein Programm dieselbe Warnung während einer einzigen Ausführung viele Male auslöst. Es wäre ärgerlich, wenn ein Programm einen endlosen Strom identischer Warnungen ausgeben würde. Daher ist ein Mechanismus erforderlich, der mehrfache identische Warnungen unterdrückt.

Es ist auch wünschenswert, dass Benutzer kontrollieren können, welche Warnungen gedruckt werden. Während es im Allgemeinen nützlich ist, alle Warnungen jederzeit zu sehen, kann es Zeiten geben, in denen es unpraktisch ist, den Code in einem Produktionsprogramm sofort zu reparieren. In diesem Fall sollte es eine Möglichkeit geben, Warnungen zu unterdrücken.

Es ist auch nützlich, spezifische Warnungen während der Programmentwicklung unterdrücken zu können, z. B. wenn eine Warnung von einem Stück Drittanbieter-Code generiert wird, das nicht sofort behoben werden kann, oder wenn es keine Möglichkeit gibt, den Code zu beheben (möglicherweise wird eine Warnmeldung für ein einwandfreies Stück Code generiert). Es wäre unklug, in solchen Fällen anzubieten, alle Warnungen zu unterdrücken: der Entwickler würde Warnungen über den Rest des Codes verpassen.

Auf der anderen Seite gibt es auch Situationen, in denen einige oder alle Warnungen besser als Fehler behandelt werden sollten. So kann es beispielsweise ein lokaler Codierungsstandard sein, dass eine bestimmte abgelegte Funktion nicht verwendet werden darf. Um dies durchzusetzen, ist es nützlich, die Warnung über diese spezielle Funktion in einen Fehler umwandeln zu können, der eine Ausnahme auslöst (ohne notwendigerweise alle Warnungen in Fehler umzuwandeln).

Daher schlage ich die Einführung eines flexiblen „Warnungsfilters“ vor, der Warnungen filtern oder in Ausnahmen umwandeln kann, basierend auf

  • Wo im Code sie generiert werden (pro Paket, Modul oder Funktion)
  • Die Warnungskategorie (Warnungskategorien werden unten besprochen)
  • Eine spezifische Warnmeldung

Der Warnungsfilter muss sowohl von der Kommandozeile als auch aus Python-Code steuerbar sein.

APIs zum Ausgeben von Warnungen

  • Um eine Warnung aus Python auszugeben
    import warnings
    warnings.warn(message[, category[, stacklevel]])
    

    Das Kategorieargument, falls angegeben, muss eine Warnungskategorienklasse sein (siehe unten); es ist standardmäßig warnings.UserWarning. Dies kann eine Ausnahme auslösen, wenn die spezifische ausgegebene Warnung durch den Warnungsfilter in einen Fehler umgewandelt wird. Die Stack-Tiefe kann von Wrapper-Funktionen verwendet werden, die in Python geschrieben sind, wie z. B.

    def deprecation(message):
        warn(message, DeprecationWarning, level=2)
    

    Dies bewirkt, dass sich die Warnung auf den Aufrufer von deprecation() bezieht und nicht auf die Quelle von deprecation() selbst (da letzteres den Zweck der Warnmeldung verfehlen würde).

  • Um eine Warnung aus C auszugeben
    int PyErr_Warn(PyObject *category, char *message);
    

    Gibt normalerweise 0 zurück, 1, wenn eine Ausnahme ausgelöst wird (entweder weil die Warnung in eine Ausnahme umgewandelt wurde oder aufgrund einer Fehlfunktion der Implementierung, z. B. Speichermangel). Das Kategorieargument muss eine Warnungskategorienklasse sein (siehe unten) oder NULL, in diesem Fall ist es standardmäßig PyExc_RuntimeWarning. Wenn die Funktion PyErr_Warn() 1 zurückgibt, sollte der Aufrufer eine normale Ausnahmebehandlung durchführen.

    Die aktuelle C-Implementierung von PyErr_Warn() importiert das Warnungsmodul (in Python implementiert) und ruft dessen warn()-Funktion auf. Dies minimiert die Menge an C-Code, die hinzugefügt werden muss, um die Warnfunktion zu implementieren.

    [XXX Offene Frage: Was ist mit der Ausgabe von Warnungen während des Lexing oder Parsens, bei denen die Ausnahme-Mechanismen nicht verfügbar sind?]

Warnungskategorien

Es gibt eine Reihe von integrierten Ausnahmen, die Warnungskategorien darstellen. Diese Kategorisierung ist nützlich, um Gruppen von Warnungen herausfiltern zu können. Die folgenden Warnungskategorienklassen sind derzeit definiert

  • Warning – dies ist die Basisklasse aller Warnungskategorienklassen und selbst eine Unterklasse von Exception
  • UserWarning – die Standardkategorie für warnings.warn()
  • DeprecationWarning – Basis-Kategorie für Warnungen über abgelegte Funktionen
  • SyntaxWarning – Basis-Kategorie für Warnungen über zweifelhafte Syntaxmerkmale
  • RuntimeWarning – Basis-Kategorie für Warnungen über zweifelhafte Laufzeitmerkmale

[XXX: Während der Überprüfungsphase für diese PEP können weitere Warnungskategorien vorgeschlagen werden.]

Diese Standard-Warnungskategorien sind aus C als PyExc_Warning, PyExc_UserWarning usw. verfügbar. Aus Python sind sie im Modul __builtin__ verfügbar, sodass kein Import erforderlich ist.

Benutzercode kann zusätzliche Warnungskategorien definieren, indem er eine der Standard-Warnungskategorien ableitet. Eine Warnungskategorie muss immer eine Unterklasse der Klasse Warning sein.

Der Warnungsfilter

Der Warnungsfilter steuert, ob Warnungen ignoriert, angezeigt oder in Fehler umgewandelt werden (Auslösen einer Ausnahme).

Der Warnungsfilter hat drei Seiten

  • Die Datenstrukturen, die verwendet werden, um die Disposition eines bestimmten Aufrufs von warnings.warn() oder PyErr_Warn() effizient zu bestimmen.
  • Die API zur Steuerung des Filters aus Python-Quellcode.
  • Die Kommandozeilenschalter zur Steuerung des Filters.

Der Warnungsfilter arbeitet in mehreren Stufen. Er ist für den (voraussichtlich häufigen) Fall optimiert, dass dieselbe Warnung immer wieder vom selben Ort im Code ausgegeben wird.

Zuerst sammelt der Warnungsfilter das Modul und die Zeilennummer, an der die Warnung ausgegeben wird; diese Informationen sind über sys._getframe() leicht verfügbar.

Konzeptionell unterhält der Warnungsfilter eine geordnete Liste von Filterspezifikationen; jede spezifische Warnung wird nacheinander mit jeder Filterspezifikation in der Liste abgeglichen, bis eine Übereinstimmung gefunden wird; die Übereinstimmung bestimmt die Disposition der Übereinstimmung. Jeder Eintrag ist ein Tupel wie folgt

(category, message, module, lineno, action)
  • category ist eine Klasse (eine Unterklasse von warnings.Warning), deren Warnungskategorie eine Unterklasse sein muss, um übereinzustimmen
  • message ist ein kompilierter regulärer Ausdruck, mit dem die Warnmeldung übereinstimmen muss (der Abgleich ist nicht case-sensitiv)
  • module ist ein kompilierter regulärer Ausdruck, mit dem der Modulname übereinstimmen muss
  • lineno ist eine Ganzzahl, mit der die Zeilennummer, an der die Warnung auftrat, übereinstimmen muss, oder 0, um alle Zeilennummern abzugleichen
  • action ist eine der folgenden Zeichenketten
    • „error“ – wandelt übereinstimmende Warnungen in Ausnahmen um
    • „ignore“ – druckt übereinstimmende Warnungen nie
    • „always“ – druckt übereinstimmende Warnungen immer
    • „default“ – druckt das erste Vorkommen übereinstimmender Warnungen für jeden Ort, an dem die Warnung ausgegeben wird
    • „module“ – druckt das erste Vorkommen übereinstimmender Warnungen für jedes Modul, in dem die Warnung ausgegeben wird
    • „once“ – druckt nur das erste Vorkommen übereinstimmender Warnungen

Da die Klasse Warning von der integrierten Klasse Exception abgeleitet ist, um eine Warnung in einen Fehler umzuwandeln, lösen wir einfach category(message) aus.

Warnungsausgabe und Formatierungs-Hooks

Wenn der Warnungsfilter beschließt, eine Warnung auszugeben (aber nicht, wenn er beschließt, eine Ausnahme auszulösen), übergibt er die Informationen über die Funktion warnings.showwarning(message, category, filename, lineno). Die Standardimplementierung dieser Funktion schreibt den Warnungstext nach sys.stderr und zeigt die Quellzeile der Datei an. Sie hat ein optionales 5. Argument, das verwendet werden kann, um eine andere Datei als sys.stderr anzugeben.

Die Formatierung von Warnungen wird von einer separaten Funktion durchgeführt, warnings.formatwarning(message, category, filename, lineno). Diese gibt eine Zeichenkette zurück (die Zeilenumbrüche enthalten kann und mit einem Zeilenumbruch endet), die gedruckt werden kann, um die gleiche Wirkung wie die Funktion showwarning() zu erzielen.

API zur Manipulation von Warnungsfiltern

warnings.filterwarnings(message, category, module, lineno, action)

Dies überprüft die Typen der Argumente, kompiliert die regulären Ausdrücke für Nachricht und Modul und fügt sie als Tupel vor dem Warnungsfilter ein.

warnings.resetwarnings()

Setzt den Warnungsfilter zurück auf leer.

Kommandozeilensyntax

Es sollte Kommandozeilenoptionen geben, um die gängigsten Filteraktionen zu spezifizieren, von denen ich erwarte, dass sie zumindest Folgendes umfassen

  • alle Warnungen unterdrücken
  • eine bestimmte Warnmeldung überall unterdrücken
  • alle Warnungen in einem bestimmten Modul unterdrücken
  • alle Warnungen in Ausnahmen umwandeln

Ich schlage die folgende Kommandozeilenoptionssyntax vor

-Waction[:message[:category[:module[:lineno]]]]

Wo

  • „action“ ist eine Abkürzung für eine der erlaubten Aktionen („error“, „default“, „ignore“, „always“, „once“ oder „module“)
  • „message“ ist eine Nachrichtenzeichenkette; passt zu Warnungen, deren Nachrichtentext ein Anfangsteil von „message“ ist (Abgleich nicht case-sensitiv)
  • „category“ ist eine Abkürzung für einen Namen einer Standard-Warnungskategorienklasse **oder** ein vollständig qualifizierter Name für eine benutzerdefinierte Warnungskategorienklasse in der Form [paket.]modul.klassenname
  • „module“ ist ein Modulname (möglicherweise paket.modul)
  • „lineno“ ist eine ganzzahlige Zeilennummer

Alle Teile außer „action“ können weggelassen werden, wobei ein leerer Wert nach Entfernung von Leerzeichen gleichbedeutend mit einem weggelassenen Wert ist.

Der C-Code, der die Python-Kommandozeile parst, speichert den Inhalt aller -W-Optionen in einer Liste von Zeichenketten, die dem Warnungsmodul als sys.warnoptions zur Verfügung gestellt wird. Das Warnungsmodul parst diese beim ersten Import. Fehler, die beim Parsen von sys.warnoptions auftreten, sind nicht fatal; eine Meldung wird an sys.stderr geschrieben und die Verarbeitung wird mit der Option fortgesetzt.

Beispiele

-Werror
Alle Warnungen in Fehler umwandeln
-Wall
Alle Warnungen anzeigen
-Wignore
Alle Warnungen ignorieren
-Wi:hallo
Warnungen ignorieren, deren Nachrichtentext mit „hallo“ beginnt
-We::Deprecation
Deprecation-Warnungen in Fehler umwandeln
-Wi:::spam:10
Alle Warnungen in Zeile 10 des Moduls spam ignorieren
-Wi:::spam -Wd:::spam:10
Alle Warnungen im Modul spam ignorieren, außer in Zeile 10
-We::Deprecation -Wd::Deprecation:spam
Deprecation-Warnungen in Fehler umwandeln, außer im Modul spam

Offene Fragen

Einige offene Fragen aus dem Stegreif

  • Was ist mit der Ausgabe von Warnungen während des Lexing oder Parsens, bei denen die Ausnahme-Mechanismen nicht verfügbar sind?
  • Die vorgeschlagene Kommandozeilensyntax ist etwas unansehnlich (obwohl die einfachen Fälle nicht so schlimm sind: -Werror, -Wignore usw.). Hat jemand eine bessere Idee?
  • Ich bin etwas besorgt, dass die Filterangaben zu komplex sind. Vielleicht würde das Filtern nur nach Kategorie und Modul (nicht nach Nachrichtentext und Zeilennummer) ausreichen?
  • Es gibt ein gewisses Durcheinander zwischen Modulnamen und Dateinamen. Die Berichterstattung verwendet Dateinamen, aber die Filterspezifikation verwendet Modulnamen. Vielleicht sollte sie auch Dateinamen zulassen?
  • Ich bin überhaupt nicht davon überzeugt, dass Pakete richtig behandelt werden.
  • Brauchen wir mehr Standard-Warnungskategorien? Weniger?
  • Um den Start-Overhead zu minimieren, wird das Warnungsmodul beim ersten Aufruf von PyErr_Warn() importiert. Es führt beim Import das Kommandozeilen-Parsing für -W Optionen durch. Daher ist es möglich, dass warnungsfreie Programme keine Fehlermeldungen für ungültige -W Optionen ausgeben.

Abgelehnte Bedenken

Paul Prescod, Barry Warsaw und Fred Drake haben mehrere zusätzliche Bedenken geäußert, die ich nicht für kritisch halte. Ich gehe hier darauf ein (die Bedenken sind paraphrasiert, nicht ihre genauen Worte)

  • Paul: warn() sollte ein Built-in oder eine Anweisung sein, um es leicht verfügbar zu machen.

    Antwort: „from warnings import warn“ ist einfach genug.

  • Paul: Was ist, wenn ich ein geschwindigkeitskritische Modul habe, das Warnungen in einer inneren Schleife auslöst. Es sollte möglich sein, den Overhead für die Erkennung der Warnung zu deaktivieren (nicht nur die Warnung zu unterdrücken).

    Antwort: schreiben Sie die innere Schleife neu, um die Warnung zu vermeiden.

  • Paul: Was ist, wenn ich den vollständigen Kontext einer Warnung sehen möchte?

    Antwort: verwenden Sie -Werror, um sie in eine Ausnahme umzuwandeln.

  • Paul: Ich bevorzuge „:*:*:“ gegenüber „:::“, um Teile der Warnungsspezifikation wegzulassen.

    Antwort: Ich nicht.

  • Barry: Es wäre schön, wenn lineno eine Bereichsspezifikation sein könnte.

    Antwort: Zu viel Komplexität bereits.

  • Barry: Ich möchte meine eigene Warnungsaktion hinzufügen. Vielleicht könnte „action“ ein Callable und eine Zeichenkette sein. Dann könnte ich in meiner IDE das auf „mygui.popupWarningsDialog“ setzen.

    Antwort: Zu diesem Zweck würden Sie warnings.showwarning() überschreiben.

  • Fred: Warum müssen die Warnungskategorienklassen in __builtin__ sein?

    Antwort: das ist die einfachste Implementierung, da die Warnungskategorien in C verfügbar sein müssen, bevor der erste PyErr_Warn() Aufruf das Warnungsmodul importiert. Ich sehe kein Problem damit, sie als Built-ins verfügbar zu machen.

Implementierung

Hier ist eine Prototyp-Implementierung: http://sourceforge.net/patch/?func=detailpatch&patch_id=102715&group_id=5470


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

Zuletzt geändert: 2025-02-01 08:55:40 GMT