Gestione delle eccezioni in Python - Sto sbagliando (e perché?)

5

Ho letto molte domande e articoli sulla gestione delle eccezioni in Python (e in generale), ma continuo a pensare che sia la cosa più confusa di sempre. Ho finito per fare qualcosa del genere:

# error class for exceptions specific to this script
class MyError(Exception): pass

# error class for exceptions in MyClass,
# which can be used from outside of this script
class MyClassError(Exception): pass

class MyClass:
    ...
    # class has method that takes a function as a parameter
    def method(self, param_function):
        ...
        param_function(a, b, c, d)
        try:
            some_other_funtion(arg)
        except ValueError, e:
            raise MyClassError("Some message")
        ...

# outside of MyClass
if __name__ == "__main__":

    # read input from console etc.
    # ...   

    def function(a, b, c, d):
       # ....
        try:
            # something with a
        except ValueError, e:
            raise MyError("My message:" + a)
        try:
            # something with b
        except ValueError, e:
            raise MyError("My message:" + b)
       try:
            # something with c
        except ValueError, e:
            raise MyError("My message:" + c)
        try:
            # something with d 
        except ValueError, e:
            raise My Error("My slightly different message:" + d)  

    x = MyClass()
    try:
        x.method(function)     
    except (IOError, MyError, MyClassError) as e:
        print e

Spiegazione:

Voglio che l'utente dello script ottenga solo un messaggio user-friendly se l'errore è il risultato del fatto che sta trasmettendo l'input sbagliato.
I messaggi con cui sollevo le mie eccezioni personalizzate sono user-friendly e (a mio parere) contengono tutte le informazioni che voglio che l'utente veda.

Questo è anche il motivo per cui sto gestendo IOError in modo simile. Mi aspetto un IOError solo se l'utente fornisce un nome file errato e voglio che venga visualizzato qualcosa come "File non trovato: nome del file" e niente altro.

Perché questo sembra sbagliato:

Nella mia funzione principale, in function , fondamentalmente sto circondando ogni riga di codice con un try-catch e gestisco ogni eccezione in modo simile - a volte il messaggio che l'utente dovrebbe vedere è un po 'diverso, ma principalmente l'unica differenza è il parametro nel messaggio.

Domande:

Sto gestendo le mie eccezioni nei posti giusti?
Sto gestendoli nel modo giusto?
La duplicazione del codice che sto facendo in function è necessaria?

    
posta iCanLearn 27.09.2015 - 02:04
fonte

2 risposte

7

L'input dell'utente fa schifo. Non puoi fidarti di quegli utenti per ottenere qualcosa di giusto, e quindi devi gestire tutti i tipi di casi speciali che rendono la vita difficile. Detto questo, possiamo minimizzare la difficoltà con i principi generali.

Convalidare in anticipo, non spesso

Controlla l'input per la validità non appena viene letto nel tuo programma. Se si legge in una stringa che dovrebbe essere un numero, convertirlo in un numero subito e reclamare all'utente se non è un numero. Tutti i dati canaglia che non verifichi in ingresso si faranno strada nel resto del programma e produrranno bug.

Ora, non puoi sempre farlo. Ci saranno casi in cui non è possibile verificare subito le proprietà corrette e sarà necessario verificarle durante l'elaborazione successiva. Ma vuoi che avvenga il più presto possibile la verifica in modo da avere i casi speciali attorno alla logica di input centralizzata in una posizione il più possibile.

Usa schemi

Consideriamo una funzione che analizza alcuni json.

def parse_student(text):
    try: 
        data = json.parse(text)
    except ValueError as error:
        raise ParseError(error)

    if not isinstance(data, dict):
        raise ParseError("Expected an object!")

    try:
        name = data['name']
    except KeyError:
        raise ParseError('Expected a name')

    if not isinstance(name, dict):
       raise ParseError("Expected an object for name")

    try:
        first = name['first']
    except KeyError:
        raise ParseError("Expected a first name")

    if not isinstance(first, basestring):
        raise ParseError("Expected first name to be a string")

    if first == '':
        raise ParseError("Expected non-empty first name")

Era molto lavoro solo per estrarre il nome, per non parlare di altri attributi. Possiamo rendere questo molto meglio se possiamo usare uno schema JSON. Vedi: link .

Posso descrivere come appare il mio oggetto studente:

{
    "type": "object",
    "properties": {
        "name": {
            "type": "object",
            "properties": {
                  "first" : {
                       "type" : "string"
                  }
            },
            "required": "first"
        },
    } 
    "required": ["name"]
}

Quando analizzo, faccio qualcosa del tipo:

def parse_student(text):
    try: 
        data = json.parse(text)
    except ValueError as error:
        raise ParseError(error)

    try:
        validate(data, STUDENT_SCHEMA)
    except ValidationError as error:
        raise ParseError(error)

    first = data['name']['first']

Il controllo dello schema verifica la presenza della struttura di cui ho bisogno. Se l'input dell'utente ha esito negativo nello schema, il validatore dello schema produrrà un messaggio di errore che spiega esattamente cosa è stato sbagliato. Lo farà in modo più coerente e corretto se poi scrissi manualmente il codice di controllo. Una volta superata la convalida, posso semplicemente estrarre i dati dall'oggetto json, perché so che avrà la struttura corretta.

Ora, probabilmente non stai analizzando JSON. Ma potresti scoprire che puoi fare qualcosa di simile per il tuo formato che ti consente di riutilizzare la logica di convalida di base tra le diverse informazioni che ottieni.

    
risposta data 28.09.2015 - 02:28
fonte
2

Probabilmente puoi semplificare il tuo codice centralizzando la prova / eccetto la validazione di argomenti di funzione e la conversione di eccezioni nella classe di eccezioni, in uno o due decoratori, che applichi a ciascuno dei tuoi metodi e funzioni. google per decoratori python per eccezioni e decoratori python per convalidare gli argomenti e troverete esempi di stackoverflow come    questo e questo .

Spesso non è necessario convalidare esplicitamente gli argomenti del metodo come il tuo codice andando a causare eccezioni naturalmente, e questo potrebbe essere abbastanza buono, come non puoi testare per tutte le eventualità.

Ricorda quando scrivi uno script, che spesso il tuo codice sarà ancora più utile se qualcuno può includerlo come modulo di libreria, in modo che principale sia specifico per ciò che un utente vorrebbe dalla riga di comando, ma nella parte della libreria non tentare troppo di oscurare dove un'eccezione deriva da e così via.

    
risposta data 27.09.2015 - 20:08
fonte

Leggi altre domande sui tag