Łapanie wyjątku podczas używania instrukcji Python „with”

293

Ku mojemu wstydowi nie mogę wymyślić, jak obsługiwać wyjątek dla instrukcji python „with”. Jeśli mam kod:

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

Naprawdę chcę obsłużyć „wyjątek braku pliku”, aby coś zrobić. Ale nie umiem pisać

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

i nie mogę pisać

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

dołączanie „z” w instrukcji try / else nie działa inaczej: wyjątek nie jest zgłaszany. Co mogę zrobić, aby przetworzyć awarię w instrukcji „with” w języku Python?

grigoryvp
źródło
Co masz na myśli mówiąc „dołączenie” do instrukcji „try / wyjątek” nie działa inaczej: wyjątek nie został zgłoszony ” ? withOświadczenie nie magicznie złamać otaczającą try...exceptoświadczenie.
Aran-Fey
4
Co ciekawe, try-with-zasobów oświadczenie Java nie obsługują dokładnie ten przypadek użycia chcesz. docs.oracle.com/javase/tutorial/essential/exceptions/...
Nayuki

Odpowiedzi:

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'

Jeśli chcesz zastosować inną obsługę błędów niż otwarte połączenie niż działający kod, możesz:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()
Douglas Leeder
źródło
3
Jak zauważono w stackoverflow.com/questions/5205811/... , blok próbny tutaj jest naprawdę zbyt szeroki. Nie ma rozróżnienia między wyjątkami podczas tworzenia menedżera kontekstu i wyjątkami w treści instrukcji with, więc może nie być to prawidłowe rozwiązanie dla wszystkich przypadków użycia.
ncoghlan
@ncoghlan Ale możesz dodać dodatkowe try...exceptbloki wewnątrz, withaby być bliżej źródła wyjątku, który nie ma nic wspólnego open().
rbaleksandar
1
@rbaleksandar Jeśli dobrze pamiętam, mój komentarz ściśle odnosił się do pierwszego przykładu w odpowiedzi, w którym cała instrukcja zawiera blok try / try (więc nawet jeśli masz wewnętrzne bloki try / expect, wszelkie wyjątki, które pozwolą uciec wciąż uderza w zewnętrzną). Następnie Douglas dodał drugi przykład, aby uwzględnić przypadki, w których to rozróżnienie ma znaczenie.
ncoghlan
3
Czy plik zostanie zamknięty w tym przykładzie? Pytam, ponieważ został otwarty poza zasięgiem „z”.
Mike Collins,
6
@MikeCollins Wyjście z „with” spowoduje zamknięcie otwartego pliku, nawet jeśli plik jest otwarty przed „with”.
user7938784
75

Najlepszy „Pythoński” sposób, w jaki można to zrobić, wykorzystując withinstrukcję, wymieniono jako przykład nr 6 w PEP 343 , który podaje tło instrukcji.

@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()

Używany w następujący sposób:

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
źródło
38
Podoba mi się, ale wydaje mi się, że to trochę za dużo czarnej magii. Nie jest to do końca jasne dla czytelnika
Paul Seeb,
5
@PaulSeeb Dlaczego nie zdefiniowałbyś tego i nie pozwoliłbyś sobie na to za każdym razem, gdy tego potrzebujesz? Jest zdefiniowany na poziomie aplikacji i jest tak magiczny jak każdy inny menedżer kontekstu. Myślę, że ktoś używający instrukcji with zrozumiałby to wyraźnie (nazwa funkcji może być bardziej wyrazista, jeśli się jej nie podoba). Sama instrukcja „with” została zaprojektowana do pracy w ten sposób, aby zdefiniować „bezpieczny” blok kodu i przekazać funkcje sprawdzające menedżerom kontekstu (aby kod był bardziej przejrzysty).
9
Cały ten problem tylko dlatego, że nie zapisałem wreszcie bloku w kodzie użytkownika. Zaczynam myśleć, że wszyscy cierpimy z powodu długiego szumu w komunikacie with.
jgomo3
1
Najlepszym sposobem obsługi wyjątków w Pythonie jest napisanie funkcji, która je przechwytuje i zwraca? Poważnie? Pytonicznym sposobem obsługi wyjątków jest użycie try...exceptinstrukcji.
Aran-Fey
58

Łapanie wyjątku podczas używania instrukcji Python „with”

Instrukcja with jest dostępna bez __future__importu od Python 2.6 . Możesz go pobrać już w Pythonie 2.5 (ale w tym momencie czas na aktualizację!) Dzięki:

from __future__ import with_statement

Oto najbliższa rzecz, którą możesz poprawić. Już prawie jesteś, ale withnie masz exceptzdania:

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

__exit__Metoda menedżera kontekstu , jeśli zwróci False, wyzeruje błąd po zakończeniu. Jeśli powróci True, to go stłumi. openWbudowane to __exit__nie wraca True, więc wystarczy zagnieździć go w próbie, z wyjątkiem bloku:

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

I standardowa płyta kotła: nie używaj gołej, except:która się chwyta BaseExceptioni każdego innego możliwego wyjątku i ostrzeżenia. Bądź co najmniej tak konkretny, jak w Exceptionprzypadku tego błędu, a może złap IOError. Przechwytuj tylko błędy, na które jesteś przygotowany.

W takim przypadku zrobiłbyś:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops
Aaron Hall
źródło
2

Rozróżnienie między możliwymi źródłami wyjątków zgłoszonych w withinstrukcji złożonej

Rozróżnianie wyjątków występujących w withinstrukcji jest trudne, ponieważ mogą pochodzić z różnych miejsc. Wyjątki można zgłaszać z jednego z następujących miejsc (lub wywoływanych tam funkcji):

  • ContextManager.__init__
  • ContextManager.__enter__
  • ciało with
  • ContextManager.__exit__

Aby uzyskać więcej informacji, zobacz dokumentację dotyczącą typów menedżera kontekstu .

Jeśli chcemy rozróżnić te różne przypadki, samo zawinięcie withw a try .. exceptnie jest wystarczające. Rozważ następujący przykład (wykorzystując ValueErrorjako przykład, ale oczywiście można go zastąpić dowolnym innym typem wyjątku):

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

Tutaj exceptwychwycą wyjątki pochodzące ze wszystkich czterech różnych miejsc, a zatem nie pozwalają na ich rozróżnienie. Jeśli przeniesiemy instancję obiektu menedżera kontekstu poza with, możemy rozróżnić __init__i 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

W rzeczywistości pomogło to w __init__części, ale możemy dodać dodatkową zmienną wartowniczą, aby sprawdzić, czy ciało withrozpoczęte do wykonania (tj. Rozróżnienie między __enter__innymi):

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

Trudna część polega na rozróżnieniu wyjątków pochodzących od BLOCKi __exit__ponieważ wyjątek, który ucieka z treści, withzostanie przekazany, __exit__który może zdecydować, jak sobie z tym poradzić (zobacz dokumenty ). Jeśli jednak się __exit__podniesie, oryginalny wyjątek zostanie zastąpiony nowym. Aby poradzić sobie z tymi przypadkami, możemy dodać w excepttekście ogólną klauzulę, withaby zapisać każdy potencjalny wyjątek, który w przeciwnym razie uniknąłby niezauważenia, i porównać go z tym, który został schwytany exceptpóźniej w skrajnym otoczeniu - jeśli są one takie same, oznacza to, że BLOCKlub w inny sposób był __exit__(w przypadku, gdy __exit__tłumi wyjątek, zwracając prawdziwą wartość na zewnątrz)except po prostu nie zostanie wykonany).

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)

Alternatywne podejście z wykorzystaniem równoważnej formy wymienionej w PEP 343

PEP 343 - Instrukcja „z” określa równoważną wersję withinstrukcji „nie z” . Tutaj możemy łatwo owinąć różne części, try ... excepta tym samym rozróżnić różne potencjalne źródła błędów:

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)

Zwykle prostsze podejście wystarczy

Konieczność takiej specjalnej obsługi wyjątków powinny być bardzo rzadko i zwykle owinięcie całości withw try ... exceptbloku będzie wystarczająca. Zwłaszcza jeśli różne źródła błędów są wskazywane przez różne (niestandardowe) typy wyjątków (menedżery kontekstów muszą być odpowiednio zaprojektowane), możemy łatwo je rozróżnić. Na przykład:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
gość
źródło