Abfangen einer Ausnahme bei Verwendung einer Python-Anweisung 'with'

293

Zu meiner Schande kann ich nicht herausfinden, wie ich mit Ausnahmen für die Python-Anweisung 'with' umgehen soll. Wenn ich einen Code habe:

with open("a.txt") as f:
    print f.readlines()

Ich möchte wirklich mit 'Datei nicht gefundene Ausnahme' umgehen, um etwas zu tun. Aber ich kann nicht schreiben

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

und kann nicht schreiben

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

Das Einfügen von 'with' in eine try / exception-Anweisung funktioniert nicht. Andernfalls wird keine Ausnahme ausgelöst. Was kann ich tun, um Fehler innerhalb der 'with'-Anweisung auf pythonische Weise zu verarbeiten?

grigoryvp
quelle
Was meinst du mit "Einschließen 'mit' in eine try / exception-Anweisung funktioniert sonst nicht: Ausnahme wird nicht ausgelöst" ? Eine withAussage bricht eine umgebende try...exceptAussage nicht auf magische Weise .
Aran-Fey
4
Interessanterweise Java Try-mit-Ressourcen - Anweisung tut unterstützen genau diesen Anwendungsfall Sie wollen. docs.oracle.com/javase/tutorial/essential/exceptions/…
Nayuki

Antworten:

256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

Wenn Sie eine andere Behandlung von Fehlern aus dem offenen Aufruf als dem Arbeitscode wünschen, können Sie Folgendes tun:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()
Douglas Leeder
quelle
3
Wie unter stackoverflow.com/questions/5205811/… angegeben , ist der try-Block hier wirklich zu breit. Beim Erstellen des Kontextmanagers wird nicht zwischen Ausnahmen und denen im Hauptteil der with-Anweisung unterschieden, sodass dies möglicherweise nicht für alle Anwendungsfälle eine gültige Lösung darstellt.
Ncoghlan
@ncoghlan Sie können jedoch zusätzliche try...exceptBlöcke hinzufügen with, um näher an der Quelle einer Ausnahme zu sein, die nichts damit zu tun hat open().
Rbaleksandar
1
@rbaleksandar Wenn ich mich richtig erinnere, bezog sich mein Kommentar streng auf das erste Beispiel in der Antwort, in dem sich die gesamte with-Anweisung im try / exception-Block befindet (selbst wenn Sie innere try / expected-Blöcke haben, werden alle Ausnahmen, die sie entkommen lassen, verschwinden traf immer noch die äußere). Douglas fügte anschließend das zweite Beispiel hinzu, um Fälle anzusprechen, in denen diese Unterscheidung von Bedeutung ist.
Ncoghlan
3
Wird die Datei in diesem Beispiel geschlossen? Ich frage, weil es außerhalb des "mit" -Bereichs geöffnet wurde.
Mike Collins
6
@MikeCollins Wenn Sie das 'mit' verlassen, wird die geöffnete Datei geschlossen, auch wenn die Datei vor dem 'mit' geöffnet ist.
user7938784
75

Der beste "pythonische" Weg, dies unter Ausnutzung der withAnweisung zu tun , ist als Beispiel Nr. 6 in PEP 343 aufgeführt , das den Hintergrund der Anweisung angibt .

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

Wird wie folgt verwendet:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")
jscs
quelle
38
Ich mag es, aber es fühlt sich wie etwas zu viel schwarze Magie an. Es ist nicht ganz explizit für den Leser
Paul Seeb
5
@PaulSeeb Warum würden Sie es nicht definieren und sich jedes Mal davor retten, es zu tun? Es ist auf Anwendungsebene definiert und genauso magisch wie jeder andere Kontextmanager. Ich denke, jemand, der die with-Anweisung verwendet, würde sie klar verstehen (der Name der Funktion könnte auch aussagekräftiger sein, wenn Sie ihn nicht mögen). Die "with" -Anweisung selbst wurde so entwickelt, dass sie auf diese Weise funktioniert, einen "sicheren" Codeblock definiert und Überprüfungsfunktionen an Kontextmanager delegiert (um den Code klarer zu machen).
9
All diese Probleme, nur weil der finally-Block nicht in den Benutzercode geschrieben wurde. Ich fange an zu denken, dass wir alle unter einem langen Hype-Symptom auf der with-Aussage leiden.
jgomo3
1
Der beste Weg, um Ausnahmen in Python zu behandeln, besteht darin, eine Funktion zu schreiben, die sie abfängt und zurückgibt. Ernsthaft? Die pythonische Methode zum Behandeln von Ausnahmen ist die Verwendung einer try...exceptAnweisung.
Aran-Fey
58

Abfangen einer Ausnahme bei Verwendung einer Python-Anweisung 'with'

Die with-Anweisung ist seit Python 2.6 ohne __future__Import verfügbar . Sie können es bereits in Python 2.5 herunterladen (aber jetzt ist es Zeit für ein Upgrade!) Mit:

from __future__ import with_statement

Hier ist das Nächste, was Sie korrigieren müssen. Sie sind fast da, haben aber withkeine exceptKlausel:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

__exit__Wenn die Methode eines Kontextmanagers zurückgegeben wird, Falsewird der Fehler nach Abschluss erneut angezeigt. Wenn es zurückkehrt True, wird es unterdrückt. Das openeingebaute Gerät __exit__kehrt nicht zurück True, Sie müssen es also nur in einem Versuch verschachteln, außer Block:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

Und Standard-Boilerplate: Verwenden Sie kein nacktes, except:das fängt BaseExceptionund jede andere mögliche Ausnahme und Warnung. Seien Sie mindestens so genau wie Exceptionund für diesen Fehler vielleicht fangen IOError. Fangen Sie nur Fehler ab, auf die Sie vorbereitet sind.

In diesem Fall würden Sie also Folgendes tun:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops
Aaron Hall
quelle
2

Unterscheidung zwischen den möglichen Ursprüngen von Ausnahmen, die aus einer zusammengesetzten withAussage hervorgehen

Die Unterscheidung zwischen Ausnahmen, die in einer withAnweisung auftreten, ist schwierig, da sie an verschiedenen Stellen auftreten können. Ausnahmen können von einer der folgenden Stellen (oder darin genannten Funktionen) ausgelöst werden:

  • ContextManager.__init__
  • ContextManager.__enter__
  • der Körper der with
  • ContextManager.__exit__

Weitere Informationen finden Sie in der Dokumentation zu Context Manager-Typen .

Wenn wir zwischen diesen verschiedenen Fällen unterscheiden wollen, reicht es nicht aus , nur das within ein zu wickeln try .. except. Betrachten Sie das folgende Beispiel (am ValueErrorBeispiel, aber es könnte natürlich durch einen anderen Ausnahmetyp ersetzt werden):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Hier exceptfängt der Wille Ausnahmen ab, die von allen vier verschiedenen Orten ausgehen, und erlaubt daher keine Unterscheidung zwischen ihnen. Wenn wir die Instanziierung des Kontextmanagerobjekts außerhalb von verschieben with, können wir unterscheiden zwischen __init__und BLOCK / __enter__ / __exit__:

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        with mgr:
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        # At this point we still cannot distinguish between exceptions raised from
        # __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
        pass

Tatsächlich hat dies nur beim __init__Teil geholfen, aber wir können eine zusätzliche Sentinel-Variable hinzufügen, um zu überprüfen, ob der Hauptteil der withAusführung ausgeführt wurde (dh zwischen __enter__und den anderen zu unterscheiden):

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        entered_body = False
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        else:
            # At this point we know the exception came either from BLOCK or from __exit__
            pass

Der schwierige Teil besteht darin, zwischen Ausnahmen zu unterscheiden, die von BLOCKund __exit__weil eine Ausnahme, die dem Hauptteil des withTestaments entgeht, übergeben wird, an __exit__die entschieden werden kann, wie damit umgegangen werden soll (siehe die Dokumente ). Wenn sich jedoch etwas __exit__auslöst, wird die ursprüngliche Ausnahme durch die neue ersetzt. Um diese Fälle zu behandeln, können wir eine allgemeine exceptKlausel in den Hauptteil der hinzufügen with, um mögliche Ausnahmen zu speichern, die sonst unbemerkt geblieben wären, und sie mit der exceptspäter im äußersten gefangenen zu vergleichen - wenn sie gleich sind, bedeutet dies, dass der Ursprung war BLOCKoder anders war es __exit__(falls __exit__die Ausnahme unterdrückt wird, indem ein wahrer Wert als äußerster zurückgegeben wirdexcept wird einfach nicht ausgeführt).

try:
    mgr = ContextManager()  # __init__ could raise
except ValueError as err:
    print('__init__ raised:', err)
else:
    entered_body = exc_escaped_from_body = False
    try:
        with mgr:
            entered_body = True  # __enter__ did not raise at this point
            try:
                BLOCK
            except TypeError:  # catching another type (which we want to handle here)
                pass
            except Exception as err:  # this exception would normally escape without notice
                # we store this exception to check in the outer `except` clause
                # whether it is the same (otherwise it comes from __exit__)
                exc_escaped_from_body = err
                raise  # re-raise since we didn't intend to handle it, just needed to store it
    except ValueError as err:
        if not entered_body:
            print('__enter__ raised:', err)
        elif err is exc_escaped_from_body:
            print('BLOCK raised:', err)
        else:
            print('__exit__ raised:', err)

Alternativer Ansatz unter Verwendung der in PEP 343 genannten äquivalenten Form

PEP 343 - Die "with" -Anweisung gibt eine äquivalente "non-with" -Version der withAnweisung an. Hier können wir die verschiedenen Teile leicht umwickeln try ... exceptund so zwischen den verschiedenen möglichen Fehlerquellen unterscheiden:

import sys

try:
    mgr = ContextManager()
except ValueError as err:
    print('__init__ raised:', err)
else:
    try:
        value = type(mgr).__enter__(mgr)
    except ValueError as err:
        print('__enter__ raised:', err)
    else:
        exit = type(mgr).__exit__
        exc = True
        try:
            try:
                BLOCK
            except TypeError:
                pass
            except:
                exc = False
                try:
                    exit_val = exit(mgr, *sys.exc_info())
                except ValueError as err:
                    print('__exit__ raised:', err)
                else:
                    if not exit_val:
                        raise
        except ValueError as err:
            print('BLOCK raised:', err)
        finally:
            if exc:
                try:
                    exit(mgr, None, None, None)
                except ValueError as err:
                    print('__exit__ raised:', err)

Normalerweise reicht ein einfacherer Ansatz aus

Die Notwendigkeit einer solchen speziellen Ausnahmebehandlung sollte ziemlich selten sein, und normalerweise ist es ausreichend, das Ganze within einen try ... exceptBlock zu wickeln . Insbesondere wenn die verschiedenen Fehlerquellen durch unterschiedliche (benutzerdefinierte) Ausnahmetypen angezeigt werden (die Kontextmanager müssen entsprechend gestaltet sein), können wir leicht zwischen ihnen unterscheiden. Zum Beispiel:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
ein Gast
quelle