Domanda

In generale , supponiamo che tu abbia un metodo come il seguente.

def intersect_two_lists(self, list1, list2):
    if not list1:
        self.trap_error("union_two_lists: list1 must not be empty.")
        return False
    if not list2:
        self.trap_error("union_two_lists: list2 must not be empty.")
        return False
    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

In questo particolare metodo quando vengono rilevati errori, in questo caso non vorrei restituire l'elenco vuoto perché quella potrebbe essere stata la vera risposta a questa specifica chiamata di metodo, voglio restituire qualcosa per indicare che i parametri erano errati. Quindi ho restituito False in caso di errore in questo caso e un elenco altrimenti (vuoto o no).

La mia domanda è: qual è la migliore pratica in aree come questa e non solo per gli elenchi? Restituisci quello che diavolo voglio e assicurati di documentarlo affinché un utente possa leggerlo? :-) Cosa fanno la maggior parte di voi gente:

  1. Se in caso di successo dovresti restituire True o False e riscontri un errore?
  2. Se in caso di successo dovresti restituire un elenco e riscontri un errore?
  3. Se in caso di successo avresti dovuto restituire un handle di file e hai rilevato un errore?
  4. eccetera
È stato utile?

Soluzione

Innanzitutto, qualunque cosa tu faccia non restituisce un risultato e un messaggio di errore. Questo è davvero un brutto modo di gestire gli errori e ti causerà infiniti mal di testa. Se devi indicare un errore solleva sempre un'eccezione .

Di solito tendo ad evitare di generare errori a meno che non sia necessario. Nel tuo esempio non è necessario lanciare un errore. L'intersezione di un elenco vuoto con uno non vuoto non è un errore. Il risultato è solo un elenco vuoto ed è corretto. Ma supponiamo che tu voglia gestire altri casi. Ad esempio se il metodo ha un tipo non di elenco. In questo caso è meglio sollevare un'eccezione. Le eccezioni non hanno nulla di cui aver paura.

Il mio consiglio per te è quello di guardare la libreria Python per funzioni simili e vedere come Python gestisce quei casi speciali. Ad esempio, dai un'occhiata al metodo di intersezione in set, tende a perdonare. Qui sto cercando di intersecare un set vuoto con un elenco vuoto:

>>> b = []
>>> a = set()
>>> a.intersection(b)
set([])

>>> b = [1, 2]
>>> a = set([1, 3])
>>> a.intersection(b)
set([1])

Gli errori vengono generati solo quando necessario:

>>> b = 1
>>> a.intersection(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

Certo, ci sono casi in cui restituire Vero o Falso in caso di successo o fallimento può essere buono. Ma è molto importante essere coerenti. La funzione deve sempre restituire lo stesso tipo o struttura. È molto confuso avere una funzione che potrebbe restituire un elenco o un valore booleano. Oppure restituisci lo stesso tipo, ma il significato di questo valore può essere diverso in caso di errore.

Modifica

L'OP dice:

  

Voglio restituire qualcosa da indicare   i parametri erano errati.

Nulla dice che c'è un errore migliore di un'eccezione. Se si desidera indicare che i parametri non sono corretti, utilizzare le eccezioni e inserire un utile messaggio di errore. Restituire un risultato in questo caso è solo confuso. Potrebbero esserci altri casi in cui si desidera indicare che non è successo nulla ma non si tratta di un errore. Ad esempio se si dispone di un metodo che elimina le voci da una tabella e la voce richiesta per l'eliminazione non esiste. In questo caso potrebbe essere giusto restituire True o False in caso di successo o fallimento. Dipende dall'applicazione e dal comportamento previsto

Altri suggerimenti

Sarebbe meglio sollevare un'eccezione piuttosto che restituire un valore speciale. Questo è esattamente ciò per cui sono state progettate le eccezioni, per sostituire i codici di errore con un meccanismo di gestione degli errori più solido e strutturato.

class IntersectException(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

def intersect_two_lists(self, list1, list2):
    if not list1: raise IntersectException("list1 must not be empty.")
    if not list2: raise IntersectException("list2 must not be empty.")

    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

In questo caso specifico, probabilmente lascerei perdere i test. Non c'è niente di sbagliato nell'intersezione di elenchi vuoti, davvero. Anche lambda è un po 'scoraggiato in questi giorni preferendo elencare le comprensioni. Vedi Trova intersezione di due elenchi? per un paio di modi per scrivere questo senza usare < code> lambda .

Mi piace restituire una tupla:

  

(True, some_result)

     

(False, some_useful_response)

L'oggetto some_useful_response può essere utilizzato per gestire la condizione di ritorno o può servire per visualizzare informazioni di debug.

NOTA : questa tecnica si applica a valori di ritorno di qualsiasi tipo. Non dovrebbe essere confuso con casi di eccezione .

All'estremità di ricezione, devi solo decomprimere:

  

Code, Response = some_function (...)

Questa tecnica si applica al "normale" flusso di controllo: è necessario utilizzare la funzionalità di eccezione quando si verificano input / elaborazione imprevisti.

Vale anche la pena notare: questa tecnica aiuta a normalizzare i ritorni delle funzioni. Sia il programmatore che l'utente delle funzioni sanno cosa aspettarsi .

DISCLAIMER: Vengo da uno sfondo di Erlang :-)

Le eccezioni sono decisamente migliori (e più Pythonic) dei ritorni di stato. Per ulteriori informazioni al riguardo: Eccezioni e resi di stato

Il caso generale è generare eccezioni per circostanze eccezionali. Vorrei poter ricordare la citazione esatta (o chi l'ha detto), ma dovresti cercare di ottenere funzioni che accettino quanti più valori e tipi siano ragionevoli e mantengano un comportamento definito in modo molto limitato. Questa è una variante di ciò che Nadia stava parlando . Considera i seguenti usi della tua funzione:

  1. intersect_two_lists (None, None)
  2. intersect_two_lists ([], ())
  3. intersect_two_lists ('12 ',' 23 ')
  4. intersect_two_lists ([1, 2], {1: 'one', 2: 'two'})
  5. intersect_two_lists (False, [1])
  6. intersect_two_lists (None, [1])

Mi aspetto che (5) genera un'eccezione poiché passare False è un errore di tipo. Il resto, tuttavia, ha un qualche senso, ma dipende in realtà dal contratto che la funzione afferma. Se intersect_two_lists fosse definito come il ritorno dell'intersezione di due iterables , quindi qualsiasi cosa diversa da (5) dovrebbe funzionare fintanto che rendi None una rappresentazione valida del set vuoto . L'implementazione sarebbe simile a:

def intersect_two_lists(seq1, seq2):
    if seq1 is None: seq1 = []
    if seq2 is None: seq2 = []
    if not isinstance(seq1, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    if not isinstance(seq2, collections.Iterable):
        raise TypeError("seq1 is not Iterable")
    return filter(...)

Di solito scrivo funzioni di supporto che applicano qualunque sia il contratto e quindi le chiamo per controllare tutte le condizioni preliminari. Qualcosa del tipo:

def require_iterable(name, arg):
    """Returns an iterable representation of arg or raises an exception."""
    if arg is not None:
        if not isinstance(arg, collections.Iterable):
            raise TypeError(name + " is not Iterable")
        return arg
    return []

def intersect_two_lists(seq1, seq2):
    list1 = require_iterable("seq1", seq1)
    list2 = require_iterable("seq2", seq2)
    return filter(...)

Puoi anche estendere questo concetto e passare la " policy " come argomento opzionale. Non consiglierei di farlo a meno che tu non voglia abbracciare Policy Based Design . Volevo menzionarlo nel caso in cui non abbiate mai esaminato questa opzione prima.

Se il contratto per intersect_two_lists è che accetta solo due parametri list non vuoti, sii esplicito e genera eccezioni se il contratto viene violato:

def require_non_empty_list(name, var):
    if not isinstance(var, list):
        raise TypeError(name + " is not a list")
    if var == []:
        raise ValueError(name + " is empty")

def intersect_two_lists(list1, list2):
    require_non_empty_list('list1', list1)
    require_non_empty_list('list2', list2)
    return filter(...)

Penso che la morale della storia sia qualunque cosa tu faccia, fallo in modo coerente ed esplicito . Personalmente, di solito preferisco sollevare eccezioni ogni volta che un contratto viene violato o mi viene dato un valore che non posso davvero usare. Se i valori che mi vengono dati sono ragionevoli, allora provo a fare qualcosa di ragionevole in cambio. Potresti anche leggere la C ++ FAQ Lite entry sulle eccezioni. Questa particolare voce ti offre un po 'più di spunti di riflessione sulle eccezioni.

Per le raccolte (elenchi, set, dicts, ecc.) la restituzione di una raccolta vuota è la scelta ovvia perché consente alla logica del sito di chiamata di rimanere libera dalla logica difensiva. Più esplicitamente, una raccolta vuota è ancora una risposta perfettamente valida da una funzione dalla quale ti aspetti una raccolta, non devi verificare che il risultato sia di qualsiasi altro tipo e puoi continuare la tua logica di business in modo pulito.

Per i risultati non di raccolta ci sono diversi modi per gestire i resi condizionali:

  1. Come molte risposte hanno già spiegato, l'uso di Eccezioni è un modo per risolvere questo, ed è un pitone idiomatico. Tuttavia, è mia preferenza non utilizzare le eccezioni per il flusso di controllo poiché trovo che crei ambiguità sulle intenzioni di un'eccezione. Invece, solleva eccezioni in situazioni eccezionali reali.
  2. Un'altra soluzione è quella di restituire Nessuna invece del risultato atteso, ma ciò costringe un utente ad aggiungere controlli difensivi ovunque nei propri siti di chiamata, offuscando l'effettiva logica aziendale che sono cercando di eseguire effettivamente.
  3. Un terzo modo è quello di utilizzare un tipo di raccolta in grado di contenere solo un singolo elemento (o essere esplicitamente vuoto). Questo si chiama Opzionale ed è il mio metodo preferito perché consente di mantenere una logica del sito di chiamata pulita. Tuttavia, python non ha un tipo opzionale incorporato, quindi uso il mio. L'ho pubblicato in una piccola libreria chiamata optional.py se qualcuno vuole provarlo. Puoi installarlo usando pip install optional.py . Accolgo con favore commenti, richieste di funzionalità e contributi.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top