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

Python Enhancement Proposals

PEP 3151 – Überarbeitung der OS- und IO-Exceptions-Hierarchie

Autor:
Antoine Pitrou <solipsis at pitrou.net>
BDFL-Delegate:
Barry Warsaw
Status:
Final
Typ:
Standards Track
Erstellt:
21. Juli 2010
Python-Version:
3.3
Post-History:

Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Die Standard-Exception-Hierarchie ist ein wichtiger Bestandteil der Python-Sprache. Sie hat zwei definierende Eigenschaften: Sie ist sowohl generisch als auch selektiv. Generisch in dem Sinne, dass der gleiche Exception-Typ ausgelöst – und abgefangen – werden kann, unabhängig vom Kontext (zum Beispiel, ob Sie versuchen, etwas zu einer Ganzzahl zu addieren, eine String-Methode aufzurufen oder ein Objekt auf einem Socket zu schreiben, eine TypeError wird für ungültige Argumenttypen ausgelöst). Selektiv in dem Sinne, dass sie dem Benutzer ermöglicht, spezifische Arten von Fehlerbedingungen einfach zu behandeln (zu unterdrücken, zu untersuchen, zu verarbeiten, zu speichern oder zu kapseln...), während andere Fehler in höhere Aufrufszenarien durchsickern. Zum Beispiel können Sie ZeroDivisionErrors abfangen, ohne die Standardbehandlung anderer ArithmeticErrors (wie OverflowErrors) zu beeinträchtigen.

Diese PEP schlägt Änderungen an einem Teil der Exception-Hierarchie vor, um die oben genannten Qualitäten besser zu verkörpern: die Fehler im Zusammenhang mit Betriebssystemaufrufen (OSError, IOError, mmap.error, select.error und alle ihre Unterklassen).

Begründung

Fehlende granulare Exceptions

Die aktuelle Vielfalt von OS-bezogenen Ausnahmen erlaubt es dem Benutzer nicht, gewünschte Fehlertypen einfach zu filtern. Betrachten Sie als Beispiel die Aufgabe, eine Datei zu löschen, wenn sie existiert. Das LBYL-Idiom (Look Before You Leap) leidet unter einer offensichtlichen Race Condition

if os.path.exists(filename):
    os.remove(filename)

Wenn eine Datei mit dem Namen filename von einem anderen Thread oder Prozess zwischen den Aufrufen von os.path.exists und os.remove erstellt wird, wird sie nicht gelöscht. Dies kann zu Fehlern in der Anwendung oder sogar zu Sicherheitsproblemen führen.

Daher besteht die Lösung darin, zu versuchen, die Datei zu entfernen und den Fehler zu ignorieren, wenn die Datei nicht existiert (ein Idiom, das als „Besser um Vergebung bitten als um Erlaubnis fragen“ (EAFP) bekannt ist). Sorgfältiger Code würde wie folgt aussehen (was sowohl unter POSIX als auch unter Windows-Systemen funktioniert)

try:
    os.remove(filename)
except OSError as e:
    if e.errno != errno.ENOENT:
        raise

oder sogar

try:
    os.remove(filename)
except EnvironmentError as e:
    if e.errno != errno.ENOENT:
        raise

Das ist viel Tipparbeit, und zwingt den Benutzer auch, sich die verschiedenen kryptischen Mnemonics aus dem errno-Modul zu merken. Es auferlegt eine zusätzliche kognitive Belastung und wird ziemlich schnell ermüdend. Folglich schreiben viele Programmierer stattdessen den folgenden Code, der Ausnahmen zu breit unterdrückt

try:
    os.remove(filename)
except OSError:
    pass

os.remove kann eine OSError nicht nur auslösen, wenn die Datei nicht existiert, sondern auch in anderen möglichen Situationen (z. B. der Dateiname verweist auf ein Verzeichnis oder der aktuelle Prozess hat keine Berechtigung, die Datei zu löschen), die alle Fehler in der Anwendungslogik anzeigen und daher nicht unterdrückt werden sollten. Was der Programmierer stattdessen schreiben möchte, ist etwas wie

try:
    os.remove(filename)
except FileNotFoundError:
    pass

Kompatibilitätsstrategie

Die Überarbeitung der Exception-Hierarchie wird offensichtlich die exakten Semantiken mindestens einiger bestehender Codes ändern. Während es nicht möglich ist, die aktuelle Situation ohne Änderung der exakten Semantiken zu verbessern, ist es möglich, eine engere Art der Kompatibilität zu definieren, die wir als nützliche Kompatibilität bezeichnen.

Dafür müssen wir zuerst erklären, was wir als vorsichtige und unvorsichtige Exception-Behandlung bezeichnen werden. Unvorsichtiger (oder „ naiver“) Code ist definiert als Code, der blind irgendwelche von OSError, IOError, socket.error, mmap.error, WindowsError, select.error abfängt, ohne das errno-Attribut zu überprüfen. Dies liegt daran, dass solche Exception-Typen viel zu breit sind, um etwas zu bedeuten. Jede von ihnen kann für Fehlerbedingungen ausgelöst werden, die so vielfältig sind wie: ein fehlerhafter Dateideskriptor (was normalerweise einen Programmierfehler anzeigt), ein nicht verbundener Socket (ebenso), ein Socket-Timeout, ein Dateityp-Mismatch, ein ungültiges Argument, ein Übertragungsfehler, unzureichende Berechtigungen, ein nicht existierendes Verzeichnis, ein volles Dateisystem usw.

(Darüber hinaus ist die Verwendung bestimmter dieser Ausnahmen unregelmäßig; Anhang B deckt den Fall des select-Moduls auf, das je nach Implementierung unterschiedliche Ausnahmen auslöst)

Vorsichtiger Code ist definiert als Code, der beim Abfangen einer der oben genannten Ausnahmen das errno-Attribut untersucht, um die tatsächliche Fehlerbedingung zu ermitteln und entsprechend zu handeln.

Dann können wir nützliche Kompatibilität wie folgt definieren

  • nützliche Kompatibilität macht das Abfangen von Ausnahmen nicht enger, aber sie kann für unvorsichtigen Ausnahmen abfangenden Code breiter sein. Angesichts des folgenden Snippet-Typs werden alle Ausnahmen, die vor dieser PEP abgefangen wurden, auch nach dieser PEP abgefangen, aber das Gegenteil kann falsch sein (da die Zusammenführung von OSError, IOError und anderen bedeutet, dass die except-Klausel ein etwas breiteres Netz wirft)
    try:
        ...
        os.remove(filename)
        ...
    except OSError:
        pass
    
  • nützliche Kompatibilität ändert das Verhalten von vorsichtig Ausnahmen abfangendem Code nicht. Angesichts des folgenden Snippet-Typs sollten die gleichen Fehler unterdrückt oder erneut ausgelöst werden, unabhängig davon, ob diese PEP implementiert wurde oder nicht
    try:
        os.remove(filename)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
    

Die Begründung für diesen Kompromiss ist, dass unvorsichtiger Code nicht wirklich geholfen werden kann, aber zumindest Code, der „funktioniert“, wird nicht plötzlich Fehler auslösen und abstürzen. Dies ist wichtig, da solcher Code wahrscheinlich in Skripten vorkommt, die als Cron-Jobs oder automatisierte Systemadministrationsprogramme verwendet werden.

Vorsichtiger Code hingegen sollte nicht bestraft werden. Tatsächlich ist ein Zweck dieser PEP, das Schreiben von vorsichtigem Code zu erleichtern.

Schritt 1: Zusammenfassung von Exception-Typen

Der erste Schritt der Lösung ist die Zusammenfassung bestehender Exception-Typen. Die folgenden Änderungen werden vorgeschlagen

  • aliase sowohl socket.error als auch select.error zu OSError
  • aliase mmap.error zu OSError
  • aliase sowohl WindowsError als auch VMSError zu OSError
  • aliase IOError zu OSError
  • EnvironmentError in OSError überführen

Jede dieser Änderungen erhält nicht die exakte Kompatibilität, aber sie behält nützliche Kompatibilität bei (siehe „Kompatibilität“ oben).

Jede dieser Änderungen kann einzeln akzeptiert oder abgelehnt werden, aber natürlich wird davon ausgegangen, dass die größte Wirkung erzielt werden kann, wenn dieser erste Schritt vollständig akzeptiert wird. In diesem Fall würde die IO-Exception-Unterhierarchie wie folgt aussehen

+-- OSError   (replacing IOError, WindowsError, EnvironmentError, etc.)
    +-- io.BlockingIOError
    +-- io.UnsupportedOperation (also inherits from ValueError)
    +-- socket.gaierror
    +-- socket.herror
    +-- socket.timeout

Begründung

Dieser erste Schritt präsentiert dem Benutzer nicht nur eine einfachere Landschaft, wie im Abschnitt Begründung erläutert, sondern ermöglicht auch eine bessere und vollständigere Lösung von Schritt 2 (siehe Voraussetzung).

Die Begründung für die Beibehaltung von OSError als offiziellen Namen für generische OS-bezogene Ausnahmen ist, dass diese präzise generischer ist als IOError. EnvironmentError ist umständlicher zu tippen und auch viel weniger bekannt.

Die Übersicht in Anhang B zeigt, dass IOError heute die dominante Fehlerart in der Standardbibliothek ist. Was Python-Code von Drittanbietern betrifft, so zeigt die Google Code Search, dass IOError in Benutzercode zehnmal beliebter ist als EnvironmentError und dreimal beliebter als OSError [3]. Ohne die Absicht, IOError mittelfristig zu verwerfen, ist die geringere Beliebtheit von OSError jedoch kein Problem.

Exception-Attribute

Da WindowsError in OSError zusammengeführt wird, erhält letztere unter Windows ein winerror-Attribut. Es wird unter Situationen auf None gesetzt, in denen es nicht aussagekräftig ist, wie es bereits bei den Attributen errno, filename und strerror der Fall ist (z. B. wenn OSError direkt von Python-Code ausgelöst wird).

Deprecation von Namen

Die folgenden Absätze skizzieren eine mögliche Strategie zur Deprecation alter Exception-Namen. Es wurde jedoch beschlossen, sie vorerst als Aliase beizubehalten. Diese Entscheidung könnte rechtzeitig für Python 4.0 überarbeitet werden.

eingebaute Exceptions

Die Deprecation alter eingebauter Ausnahmen kann nicht auf einfache Weise erfolgen, indem alle Lookups im Builtins-Namensraum abgefangen werden, da diese leistungskritisch sind. Wir können auch nicht auf Objektebene arbeiten, da die veralteten Namen auf nicht veraltete Objekte umbenannt werden.

Eine Lösung besteht darin, diese Namen zur Kompilierungszeit zu erkennen und dann einen separaten LOAD_OLD_GLOBAL-Opcode anstelle des regulären LOAD_GLOBAL auszugeben. Dieser spezialisierte Opcode behandelt die Ausgabe einer DeprecationWarning (oder PendingDeprecationWarning, je nach beschlossener Richtlinie), wenn der Name nicht im globalen Namensraum, sondern nur im Builtins-Namensraum existiert. Dies reicht aus, um Fehlalarme zu vermeiden (z. B. wenn jemand sein eigenes OSError in einem Modul definiert), und Fehlalarme sind selten (z. B. wenn jemand über das builtins-Modul und nicht direkt auf OSError zugreift).

modulbasierte Exceptions

Der obige Ansatz kann nicht einfach verwendet werden, da er eine spezielle Behandlung einiger Module beim Kompilieren von Codeobjekten erfordern würde. Diese Namen sind jedoch konstruktionsbedingt viel weniger sichtbar (sie erscheinen nicht im Builtins-Namensraum) und auch weniger bekannt, sodass wir entscheiden könnten, sie in ihren eigenen Namensräumen zu belassen.

Schritt 2: Definition zusätzlicher Unterklassen

Der zweite Schritt der Lösung besteht darin, die Hierarchie zu erweitern, indem Unterklassen definiert werden, die anstelle ihrer Elternklasse für spezifische errno-Werte ausgelöst werden. Welche errno-Werte zur Diskussion stehen, aber eine Übersicht über bestehende Exception-Matching-Praktiken (siehe Anhang A) hilft uns, eine angemessene Teilmenge aller Werte vorzuschlagen. Tatsächlich scheint das Zuordnen aller errno-Mnemonics töricht, sinnlos und würde den Wurzelnamensraum verschmutzen.

Darüber hinaus könnten in einigen Fällen unterschiedliche errno-Werte die gleiche Exception-Unterklasse auslösen. Zum Beispiel sind EAGAIN, EALREADY, EWOULDBLOCK und EINPROGRESS alle verwendet, um zu signalisieren, dass eine Operation auf einem nicht-blockierenden Socket blockieren würde (und daher später erneut versucht werden muss). Sie könnten daher alle eine identische Unterklasse auslösen und dem Benutzer erlauben, das errno-Attribut zu untersuchen, wenn (s)er dies wünscht (siehe unten „Exception-Attribute“).

Voraussetzung

Schritt 1 ist eine lose Voraussetzung dafür.

Voraussetzung, da einige errnos derzeit verschiedenen Exception-Klassen zugeordnet werden können: Zum Beispiel kann ENOENT sowohl OSError als auch IOError zugeordnet sein, abhängig vom Kontext. Wenn wir die nützliche Kompatibilität nicht brechen wollen, können wir keine except OSError (oder IOError) Verfehlungen beim Abgleich einer Ausnahme zulassen, wo es heute erfolgreich wäre.

Lose, da wir uns für eine partielle Lösung von Schritt 2 entscheiden könnten, wenn bestehende Exception-Klassen nicht zusammengeführt werden: Zum Beispiel könnte ENOENT eine hypothetische FileNotFoundError auslösen, wo zuvor eine IOError ausgelöst wurde, aber andernfalls weiterhin OSError auslöst.

Die Abhängigkeit von Schritt 1 könnte vollständig entfernt werden, wenn die neuen Unterklassen Mehrfachvererbung verwenden, um mit allen bestehenden Superklassen (oder zumindest OSError und IOError, die die am weitesten verbreiteten sind) übereinzustimmen. Dies würde jedoch die Hierarchie komplizierter und damit für den Benutzer schwerer verständlich machen.

Neue Exception-Klassen

Die folgende vorläufige Liste von Unterklassen, zusammen mit einer Beschreibung und der Liste der ihnen zugeordneten errnos, wird zur Diskussion gestellt

  • FileExistsError: Versuch, eine Datei oder ein Verzeichnis zu erstellen, das bereits existiert (EEXIST)
  • FileNotFoundError: für alle Umstände, unter denen eine Datei oder ein Verzeichnis angefordert wird, aber nicht existiert (ENOENT)
  • IsADirectoryError: Dateioperation (open(), os.remove()...) auf einem Verzeichnis angefordert (EISDIR)
  • NotADirectoryError: Verzeichnisoperation auf etwas anderem angefordert (ENOTDIR)
  • PermissionError: Versuch, eine Operation ohne ausreichende Zugriffsrechte durchzuführen - z. B. Dateisystemberechtigungen (EACCES, EPERM)
  • BlockingIOError: eine Operation würde auf einem Objekt (z. B. Socket) blockieren, das für den nicht-blockierenden Betrieb eingestellt ist (EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS); dies ist die bestehende io.BlockingIOError mit erweiterter Rolle
  • BrokenPipeError: Versuch, in eine Pipe zu schreiben, während das andere Ende geschlossen wurde, oder Versuch, in einen Socket zu schreiben, der zum Schreiben heruntergefahren wurde (EPIPE, ESHUTDOWN)
  • InterruptedError: ein Systemaufruf wurde durch ein eingehendes Signal unterbrochen (EINTR)
  • ConnectionAbortedError: Verbindungsversuch vom Peer abgebrochen (ECONNABORTED)
  • ConnectionRefusedError: Verbindung vom Peer zurückgewiesen (ECONNREFUSED)
  • ConnectionResetError: Verbindung vom Peer zurückgesetzt (ECONNRESET)
  • TimeoutError: Verbindung zeitüberschritten (ETIMEDOUT); dies kann als generische Timeout-Ausnahme neu klassifiziert werden und ersetzt socket.timeout und ist auch für andere Arten von Timeouts nützlich (z. B. bei Lock.acquire())
  • ChildProcessError: Operation auf einem Kindprozess fehlgeschlagen (ECHILD); dies wird hauptsächlich von der wait()-Familie von Funktionen ausgelöst.
  • ProcessLookupError: der angegebene Prozess (identifiziert z. B. durch seine Prozess-ID) existiert nicht (ESRCH).

Darüber hinaus wird die folgende Exception-Klasse zur Aufnahme vorgeschlagen

  • ConnectionError: eine Basisklasse für ConnectionAbortedError, ConnectionRefusedError und ConnectionResetError

Die folgende Zeichnung versucht, die vorgeschlagenen Ergänzungen zusammenzufassen, zusammen mit den entsprechenden errno-Werten (sofern zutreffend). Die Wurzel der Unterhierarchie (OSError, unter der Annahme, dass Schritt 1 vollständig akzeptiert wird) wird nicht gezeigt

+-- BlockingIOError        EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS
+-- ChildProcessError                                          ECHILD
+-- ConnectionError
    +-- BrokenPipeError                              EPIPE, ESHUTDOWN
    +-- ConnectionAbortedError                           ECONNABORTED
    +-- ConnectionRefusedError                           ECONNREFUSED
    +-- ConnectionResetError                               ECONNRESET
+-- FileExistsError                                            EEXIST
+-- FileNotFoundError                                          ENOENT
+-- InterruptedError                                            EINTR
+-- IsADirectoryError                                          EISDIR
+-- NotADirectoryError                                        ENOTDIR
+-- PermissionError                                     EACCES, EPERM
+-- ProcessLookupError                                          ESRCH
+-- TimeoutError                                            ETIMEDOUT

Benennung

Verschiedene Namensstreitigkeiten können entstehen. Eine davon ist, ob alle Namen von Exception-Klassen auf „Error“ enden sollten. Dafür spricht die Konsistenz mit dem Rest der Exception-Hierarchie, dagegen die Kürze (insbesondere bei langen Namen wie ConnectionAbortedError).

Exception-Attribute

Um nützliche Kompatibilität zu erhalten, sollten diese Unterklassen immer noch angemessene Werte für die verschiedenen Exception-Attribute der Oberklasse setzen (z. B. errno, filename und optional winerror).

Implementierung

Da vorgeschlagen wird, dass die Unterklassen rein basierend auf dem Wert von errno ausgelöst werden, sollten nur geringe oder gar keine Änderungen in Erweiterungsmodulen (sowohl Standard- als auch Drittanbieter-) erforderlich sein.

Die erste Möglichkeit besteht darin, die Familie der PyErr_SetFromErrno()-Funktionen (PyErr_SetFromWindowsErr() unter Windows) anzupassen, um die entsprechende OSError-Unterklasse auszulösen. Dies würde jedoch keinen Python-Code abdecken, der OSError direkt auslöst, mit dem folgenden Idiom (gesehen in Lib/tempfile.py)

raise IOError(_errno.EEXIST, "No usable temporary file name found")

Eine zweite Möglichkeit, von Marc-Andre Lemburg vorgeschlagen, ist die Anpassung von OSError.__new__, um die entsprechende Unterklasse zu instanziieren. Dies hat den Vorteil, auch Python-Code wie den obigen abzudecken.

Mögliche Einwände

Namensraumverschmutzung

Die Verfeinerung der Exception-Hierarchie vergrößert den Wurzel- (oder Builtin-) Namensraum. Dies ist jedoch zu mäßigen, da

  • nur eine Handvoll zusätzlicher Klassen vorgeschlagen wird;
  • während Standard-Exception-Typen im Wurzelnamensraum leben, werden sie visuell dadurch unterschieden, dass sie die CamelCase-Konvention verwenden, während fast alle anderen Builtins Kleinbuchstaben verwenden (außer True, False, None, Ellipsis und NotImplemented)

Eine Alternative wäre, ein separates Modul bereitzustellen, das die feingranularen Ausnahmen enthält, aber das würde den Zweck der Förderung von vorsichtigem über unvorsichtigem Code zunichtemachen, da der Benutzer zuerst das neue Modul importieren müsste, anstatt auf bereits zugängliche Namen zuzugreifen.

Frühere Diskussion

Obwohl dies der erste formelle Vorschlag dieser Art ist, hat die Idee in der Vergangenheit informelle Unterstützung erhalten [1]; sowohl die Einführung feingranularerer Exception-Klassen als auch die Zusammenfassung von OSError und IOError.

Die Entfernung von WindowsError allein wurde als Teil einer anderen PEP diskutiert und abgelehnt, aber es schien einen Konsens zu geben, dass die Unterscheidung zu OSError nicht sinnvoll war. Dies unterstützt zumindest seine Aliasing mit OSError.

Implementierung

Die Referenzimplementierung wurde in Python 3.3 integriert. Sie wurde früher unter http://hg.python.org/features/pep-3151/ im Branch pep-3151 entwickelt und auf dem Bug-Tracker unter http://bugs.python.org/issue12555 verfolgt. Sie wurde erfolgreich auf einer Vielzahl von Systemen getestet: Linux, Windows, OpenIndiana und FreeBSD Buildbots.

Eine Fehlerquelle waren die jeweiligen Konstruktoren von OSError und WindowsError, die inkompatibel waren. Die Art und Weise, wie dies gelöst wird, ist, dass die OSError-Signatur beibehalten und ein viertes optionales Argument hinzugefügt wird, um den Windows-Fehlercode (der sich vom POSIX-errno unterscheidet) zu übergeben. Das vierte Argument wird als winerror gespeichert und seine POSIX-Übersetzung als errno. Die PyErr_SetFromWindowsErr*-Funktionen wurden angepasst, um den richtigen Konstruktoraufruf zu verwenden.

Eine leichte Komplikation tritt auf, wenn die PyErr_SetExcFromWindowsErr*-Funktionen mit OSError anstelle von WindowsError aufgerufen werden: Das errno-Attribut des Ausnahmeobjekts würde den Windows-Fehlercode (wie 109 für ERROR_BROKEN_PIPE) anstelle seiner POSIX-Übersetzung (wie 32 für EPIPE) speichern, was es jetzt tut. Für Nicht-Socket-Fehlercodes tritt dies nur im privaten Modul _multiprocessing auf, für das keine Kompatibilitätsprobleme bestehen.

Hinweis

Für Socket-Fehler ist der „POSIX errno“, wie er vom errno-Modul reflektiert wird, numerisch gleich dem Windows Socket Fehlercode, der vom WSAGetLastError Systemaufruf zurückgegeben wird

>>> errno.EWOULDBLOCK
10035
>>> errno.WSAEWOULDBLOCK
10035

Mögliche Alternative

Mustererkennung

Eine weitere Möglichkeit wäre die Einführung einer erweiterten Mustererkennungssyntax beim Abfangen von Ausnahmen. Zum Beispiel

try:
    os.remove(filename)
except OSError as e if e.errno == errno.ENOENT:
    pass

Mehrere Probleme mit diesem Vorschlag

  • er führt neue Syntax ein, die vom Autor als schwerwiegendere Änderung im Vergleich zur Überarbeitung der Exception-Hierarchie angesehen wird
  • er reduziert den Tippaufwand nicht wesentlich
  • er entlastet den Programmierer nicht von der Last, sich errno-Mnemonics merken zu müssen

Von dieser PEP ignorierte Exceptions

Diese PEP ignoriert EOFError, die einen abgeschnittenen Eingabestrom in verschiedenen Protokoll- und Dateiformatimplementierungen signalisiert (z. B. GzipFile). EOFError ist keine OS- oder IO-bezogene, sondern eine logische Ausnahme auf höherer Ebene.

Diese PEP ignoriert auch SSLError, die vom ssl-Modul ausgelöst wird, um Fehler von der OpenSSL-Bibliothek weiterzugeben. Idealerweise würde SSLError eine ähnliche, aber separate Behandlung erfahren, da es eigene Konstanten für Fehlertypen definiert (ssl.SSL_ERROR_WANT_READ usw.). In Python 3.2 wird SSLError bereits durch socket.timeout ersetzt, wenn ein Socket-Timeout signalisiert wird (siehe Issue 10272).

Schließlich ist das Schicksal von socket.gaierror und socket.herror nicht geklärt. Während sie weniger kryptische Namen verdienen würden, kann dies separat von der Reorganisation der Exception-Hierarchie behandelt werden.

Anhang A: Übersicht über gängige errnos

Dies ist eine schnelle Bestandsaufnahme der verschiedenen errno-Mnemonics, die im Rahmen von except-Klauseln in der Standardbibliothek und ihren Tests geprüft werden.

Gängige errnos mit OSError

  • EBADF: fehlerhafter Dateideskriptor (bedeutet normalerweise, dass der Dateideskriptor geschlossen wurde)
  • EEXIST: Datei oder Verzeichnis existiert
  • EINTR: unterbrochener Funktionsaufruf
  • EISDIR: ist ein Verzeichnis
  • ENOTDIR: kein Verzeichnis
  • ENOENT: keine solche Datei oder kein solches Verzeichnis
  • EOPNOTSUPP: Operation wird auf Socket nicht unterstützt (mögliche Verwechslung mit dem bestehenden io.UnsupportedOperation)
  • EPERM: Operation nicht erlaubt (bei Verwendung von z. B. os.setuid())

Gängige errnos mit IOError

  • EACCES: Zugriff verweigert (für Dateisystemoperationen)
  • EBADF: fehlerhafter Dateideskriptor (mit select.epoll); Leseoperation auf einem nur schreibbaren GzipFile oder umgekehrt
  • EBUSY: Gerät oder Ressource beschäftigt
  • EISDIR: ist ein Verzeichnis (beim Versuch zu öffnen())
  • ENODEV: kein solches Gerät
  • ENOENT: keine solche Datei oder kein solches Verzeichnis (beim Versuch zu öffnen())
  • ETIMEDOUT: Verbindung zeitüberschritten

Gängige errnos mit socket.error

All diese Fehler können auch mit einer einfachen IOError verbunden sein, z. B. beim Aufruf von read() auf einem Socket-Dateideskriptor.

  • EAGAIN: Ressource vorübergehend nicht verfügbar (bei einem nicht-blockierenden Socket-Aufruf außer connect())
  • EALREADY: Verbindung bereits im Gange (bei einem nicht-blockierenden connect())
  • EINPROGRESS: Operation im Gange (bei einem nicht-blockierenden connect())
  • EINTR: unterbrochener Funktionsaufruf
  • EISCONN: der Socket ist verbunden
  • ECONNABORTED: Verbindung vom Peer abgebrochen (bei einem accept()-Aufruf)
  • ECONNREFUSED: Verbindung vom Peer verweigert
  • ECONNRESET: Verbindung vom Peer zurückgesetzt
  • ENOTCONN: Socket nicht verbunden
  • ESHUTDOWN: kann nach dem Herunterfahren des Transportendpunkts nicht senden
  • EWOULDBLOCK: gleiche Gründe wie EAGAIN

Gängige errnos mit select.error

  • EINTR: unterbrochener Funktionsaufruf

Anhang B: Übersicht über ausgelöste OS- und IO-Fehler

Über VMSError

VMSError wird vom Interpreter-Kern und der Standardbibliothek überhaupt nicht verwendet. Sie wurde als Teil der OpenVMS-Patches im Jahr 2002 von Jean-François Piéronne [4] hinzugefügt; die Motivation für die Aufnahme von VMSError war, dass sie von Drittanbieterpaketen ausgelöst werden könnte.

Interpreter-Kern

Behandlung von PYTHONSTARTUP löst IOError aus (aber der Fehler wird verworfen)

$ PYTHONSTARTUP=foox ./python
Python 3.2a0 (py3k:82920M, Jul 16 2010, 22:53:23)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Could not open PYTHONSTARTUP
IOError: [Errno 2] No such file or directory: 'foox'

PyObject_Print() löst IOError aus, wenn ferror() einen Fehler auf dem FILE * Parameter signalisiert (der im Quellcode immer stdout oder stderr ist).

Unicode-Kodierung und -Dekodierung mit der mbcs-Kodierung kann unter bestimmten Fehlerbedingungen WindowsError auslösen.

Standardbibliothek

bz2

Löst durchgehend IOError aus (OSError wird nicht verwendet)

>>> bz2.BZ2File("foox", "rb")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> bz2.BZ2File("LICENSE", "rb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: invalid data stream
>>> bz2.BZ2File("/tmp/zzz.bz2", "wb").read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: file is not ready for reading

curses

Nicht untersucht.

dbm.gnu, dbm.ndbm

_dbm.error und _gdbm.error erben von IOError

>>> dbm.gnu.open("foox")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_gdbm.error: [Errno 2] No such file or directory

fcntl

Löst durchgehend IOError aus (OSError wird nicht verwendet).

imp Modul

Löst IOError bei fehlerhaften Dateideskriptoren aus

>>> imp.load_source("foo", "foo", 123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

io Modul

Löst IOError aus, wenn unter Unix versucht wird, ein Verzeichnis zu öffnen

>>> open("Python/", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 21] Is a directory: 'Python/'

Löst IOError oder io.UnsupportedOperation (das davon erbt) für nicht unterstützte Operationen aus

>>> open("LICENSE").write("bar")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: not writable
>>> io.StringIO().fileno()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: fileno
>>> open("LICENSE").seek(1, 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: can't do nonzero cur-relative seeks

Löst entweder IOError oder TypeError aus, wenn die zugrunde liegende I/O-Schicht fehlerhaft ist (d. h. die erwartete API verletzt).

Löst IOError aus, wenn die zugrunde liegende OS-Ressource ungültig wird

>>> f = open("LICENSE")
>>> os.close(f.fileno())
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 9] Bad file descriptor

…oder für implementierungsspezifische Optimierungen

>>> f = open("LICENSE")
>>> next(f)
'A. HISTORY OF THE SOFTWARE\n'
>>> f.tell()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: telling position disabled by next() call

Löst BlockingIOError (erbt von IOError) aus, wenn ein Aufruf auf einem nicht-blockierenden Objekt blockieren würde.

mmap

Unter Unix löst es durchgehend eine eigene mmap.error aus (erbt von EnvironmentError)

>>> mmap.mmap(123, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 9] Bad file descriptor
>>> mmap.mmap(os.open("/tmp", os.O_RDONLY), 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
mmap.error: [Errno 13] Permission denied

Unter Windows löst es jedoch hauptsächlich WindowsError aus (der Quellcode zeigt auch einige Vorkommen von mmap.error)

>>> fd = os.open("LICENSE", os.O_RDONLY)
>>> m = mmap.mmap(fd, 16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 5] Accès refusé
>>> sys.last_value.errno
13
>>> errno.errorcode[13]
'EACCES'

>>> m = mmap.mmap(-1, 4096)
>>> m.resize(16384)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
WindowsError: [Error 87] Paramètre incorrect
>>> sys.last_value.errno
22
>>> errno.errorcode[22]
'EINVAL'

multiprocessing

Nicht untersucht.

os / posix

Das Modul os (oder posix) löst durchgehend OSError aus, außer unter Windows, wo stattdessen WindowsError ausgelöst werden kann.

ossaudiodev

Löst durchgehend IOError aus (OSError wird nicht verwendet)

>>> ossaudiodev.open("foo", "r")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'foo'

readline

Löst in verschiedenen Dateihandhabungsfunktionen IOError aus

>>> readline.read_history_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.read_init_file("foo")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory
>>> readline.write_history_file("/dev/nonexistent")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 13] Permission denied

select

  • select() und poll-Objekte lösen select.error aus, das von nichts erbt (aber poll.modify() löst IOError aus);
  • epoll-Objekte lösen IOError aus;
  • kqueue-Objekte lösen sowohl OSError als auch IOError aus.

Nebenbemerkung: Da select.error nicht von EnvironmentError erbt, erhält es nicht das nützliche errno-Attribut. Benutzercode muss stattdessen args[0] prüfen

>>> signal.alarm(1); select.select([], [], [])
0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
select.error: (4, 'Interrupted system call')
>>> e = sys.last_value
>>> e
error(4, 'Interrupted system call')
>>> e.errno == errno.EINTR
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'error' object has no attribute 'errno'
>>> e.args[0] == errno.EINTR
True

signal

signal.ItimerError erbt von IOError.

socket

socket.error erbt von IOError.

sys

sys.getwindowsversion() löst WindowsError mit einer ungültigen Fehlernummer aus, wenn der GetVersionEx() Aufruf fehlschlägt.

time

Löst IOError für interne Fehler in time.time() und time.sleep() aus.

zipimport

zipimporter.get_data() kann IOError auslösen.

Danksagungen

Signifikante Eingaben wurden von Alyssa Coghlan erhalten.

Referenzen


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

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