Catturare un'eccezione durante l'utilizzo di un'istruzione 'with' di Python


293

Con mia vergogna, non riesco a capire come gestire l'eccezione per l'istruzione "with" di Python. Se ho un codice:

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

Voglio davvero gestire "l'eccezione del file non trovato" per fare qualcosa. Ma non riesco a scrivere

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

e non posso scrivere

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

racchiudere 'con' in un'istruzione try / tranne non funziona diversamente: l'eccezione non viene sollevata. Cosa posso fare per elaborare il fallimento all'interno dell'istruzione 'with' in modo Pythonic?


Cosa intendi per "racchiudere" con "in un'istruzione try / tranne non funziona diversamente: l'eccezione non viene sollevata" ? Una withdichiarazione non magicamente rompere un circostante try...exceptdichiarazione.
Aran-Fey,

4
È interessante notare che, try-con-le risorse dichiarazione di Java fa sostenere esattamente questo caso d'uso che si desidera. docs.oracle.com/javase/tutorial/essential/exceptions/…
Nayuki,

Risposte:


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'

Se si desidera una gestione diversa degli errori dalla chiamata aperta rispetto al codice di lavoro, è possibile:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()

3
Come notato in stackoverflow.com/questions/5205811/… , il blocco try qui è davvero troppo ampio. Non viene fatta alcuna distinzione tra le eccezioni durante la creazione del gestore di contesto e quelle nel corpo dell'istruzione with, quindi potrebbe non essere una soluzione valida per tutti i casi d'uso.
ncoghlan,

@ncoghlan Ma puoi aggiungere try...exceptblocchi extra all'interno withper essere più vicino alla fonte di un'eccezione che non ha nulla a che fare con open().
rbaleksandar,

1
@rbaleksandar Se ricordo bene, il mio commento si riferiva strettamente al primo esempio nella risposta, in cui l'intera istruzione with si trova all'interno del blocco try / tranne (quindi anche se hai blocchi try / prevedono interni, tutte le eccezioni che lasciano scappare colpire ancora quello esterno). Douglas ha successivamente aggiunto il secondo esempio per affrontare i casi in cui tale distinzione è importante.
ncoghlan,

3
Il file verrà chiuso in questo esempio? Chiedo perché è stato aperto al di fuori dell'ambito "con".
Mike Collins,

6
@MikeCollins L'uscita da 'con' chiuderà il file aperto anche quando il file è aperto prima di 'con'.
user7938784

75

Il miglior modo "Pythonic" per farlo, sfruttando l' withistruzione, è elencato come Esempio n. 6 in PEP 343 , che fornisce lo sfondo dell'istruzione.

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

Usato come segue:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")

38
Mi piace ma sembra un po 'troppa magia nera. Non è del tutto esplicito per il lettore
Paul Seeb,

5
@PaulSeeb Perché non dovresti definirlo e salvarti dal farlo ogni volta che è necessario? È definito a livello di applicazione ed è magico come qualsiasi altro gestore di contesto. Penso che qualcuno che usa l'istruzione with lo capirà chiaramente (il nome della funzione potrebbe anche essere più espressivo se non ti piace). L'istruzione "with" stessa è stata progettata per funzionare in questo modo, per definire un blocco di codice "sicuro" e delegare le funzioni di controllo ai gestori di contesto (per rendere il codice più chiaro).

9
Tutto questo problema solo per non aver scritto il blocco finally nel codice utente. Sto cominciando a pensare che tutti soffriamo per un lungo sintomo di hype sull'affermazione with.
jgomo3,

1
Il modo migliore per gestire le eccezioni in Python è scrivere una funzione che le cattura e le restituisce? Sul serio? Il modo pitonico di gestire le eccezioni è usare try...exceptun'istruzione.
Aran-Fey,

58

Catturare un'eccezione durante l'utilizzo di un'istruzione 'with' di Python

L'istruzione with è disponibile senza __future__importazione da Python 2.6 . Puoi ottenerlo già da Python 2.5 (ma a questo punto è tempo di aggiornare!) Con:

from __future__ import with_statement

Ecco la cosa più vicina per correggere che hai. Ci sei quasi, ma withnon ha una exceptclausola:

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

Il __exit__metodo di un gestore di contesto , se restituito, ripristinerà Falsel'errore al termine. Se ritorna True, lo sopprimerà. Il openbuiltin __exit__non ritorna True, quindi devi solo annidarlo in un tentativo, tranne il blocco:

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

E boilerplate standard: non usare un nudo except:che cattura BaseExceptione ogni altra possibile eccezione e avvertenza. Sii almeno tanto specifico quanto Exception, e per questo errore, forse cattura IOError. Prendi solo gli errori che sei pronto a gestire.

Quindi, in questo caso, dovresti fare:

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

2

Differenziazione tra le possibili origini delle eccezioni sollevate da una withdichiarazione composta

La differenziazione tra le eccezioni che si verificano in withun'istruzione è complicata perché possono avere origine in luoghi diversi. Eccezioni possono essere sollevate da uno dei seguenti luoghi (o funzioni chiamate in esso):

  • ContextManager.__init__
  • ContextManager.__enter__
  • il corpo del with
  • ContextManager.__exit__

Per ulteriori dettagli, consultare la documentazione sui tipi di Gestore di contesto .

Se vogliamo distinguere tra questi diversi casi, basta avvolgere within a try .. exceptnon è sufficiente. Considera il seguente esempio (usando ValueErrorcome esempio ma ovviamente potrebbe essere sostituito con qualsiasi altro tipo di eccezione):

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

Qui exceptcattureranno le eccezioni che hanno origine in tutti e quattro i diversi luoghi e quindi non consentono di distinguerli. Se spostiamo l'istanza dell'oggetto gestore di contesto all'esterno di with, possiamo distinguere tra __init__e 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

In effetti, questo ha aiutato la __init__parte, ma possiamo aggiungere una variabile sentinella aggiuntiva per verificare se il corpo withdell'inizio ha iniziato l'esecuzione (ovvero differenziando tra __enter__e gli altri):

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

La parte difficile è quella di distinguere tra le eccezioni originate da BLOCKe __exit__perché withviene passata un'eccezione che sfugge al corpo della volontà __exit__che può decidere come gestirla (vedi i documenti ). Se tuttavia si __exit__solleva, l'eccezione originale verrà sostituita da quella nuova. Per far fronte a questi casi, possiamo aggiungere una exceptclausola generale nel corpo dell'archivio withper archiviare qualsiasi potenziale eccezione che altrimenti sarebbe sfuggita inosservata e confrontarla con quella rilevata in exceptseguito più esterna - se sono uguali ciò significa che l'origine era BLOCKo altrimenti lo era __exit__(nel caso __exit__sopprimesse l'eccezione restituendo un valore vero il più esternoexcept semplicemente non verrà eseguito).

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)

Approccio alternativo usando la forma equivalente menzionata nel PEP 343

PEP 343 - L'istruzione "with" specifica una versione equivalente "non con" withdell'istruzione. Qui possiamo facilmente avvolgere le varie parti con try ... excepte quindi differenziare tra le diverse potenziali fonti di errore:

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)

Di solito un approccio più semplice andrà bene

La necessità di una simile gestione delle eccezioni dovrebbe essere piuttosto rara e normalmente sarà sufficiente avvolgere il tutto within un try ... exceptblocco. Soprattutto se le varie fonti di errore sono indicate da diversi tipi di eccezione (personalizzati) (i gestori di contesto devono essere progettati di conseguenza) possiamo facilmente distinguerli. Per esempio:

try:
    with ContextManager():
        BLOCK
except InitError:  # raised from __init__
    ...
except AcquireResourceError:  # raised from __enter__
    ...
except ValueError:  # raised from BLOCK
    ...
except ReleaseResourceError:  # raised from __exit__
    ...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.