Meilleure pratique en python pour la valeur de retour sur l'erreur par rapport au succès

Dans général , disons que vous avez une méthode comme ci-dessous.

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)

dans cette méthode particulière quand des erreurs sont trouvées, Je ne voudrais pas retourner la liste vide dans ce cas parce que cela aurait pu être la vraie réponse à cet appel de méthode spécifique, je veux retourner quelque chose pour indiquer que les paramètres étaient incorrects. Donc J'ai retourné False Sur erreur dans ce cas, et une liste autrement (vide ou pas).

My la question Est, Quelle est la meilleure pratique dans des domaines comme celui-ci, et pas seulement pour les listes?Retourner tout ce que je veux et m'assurer que je documente pour un utilisateur de lire? :-) Ce que font la plupart des gens de faire:

  1. si, sur la réussite, vous étiez censé retourner vrai ou faux et vous attrapez une erreur?
  2. si sur le succès vous étiez censé retourner une liste et vous attrapez une erreur?
  3. si après succès vous étiez censé retourner un dossier manier et vous attrapez une erreur?
  4. et cetera
41
demandé sur Bhargav Rao 2009-10-27 16:11:15

6 réponses

tout d'abord, quoi que vous fassiez, ne retournez pas un résultat et un message d'erreur. C'est une très mauvaise façon de gérer les erreurs et vous causera des maux de tête sans fin. si vous avez besoin d'indiquer une erreur, faites toujours une exception .

j'ai généralement tendance à éviter de soulever des erreurs à moins que ce ne soit nécessaire. Dans votre exemple, de lancer une erreur n'est pas vraiment nécessaire. L'intersection d'une liste vide non vide n'est pas une erreur. Le résultat est tout simplement vide de la liste et qui est correct. Mais disons que vous voulez pour gérer d'autres cas. Par exemple si la méthode a un type non-list. Dans ce cas, il est préférable de lever une exception. Une Exception ne sont rien à craindre.

mon conseil pour vous est de regarder la bibliothèque Python pour des fonctions similaires et voir comment Python gère ces cas spéciaux. Par exemple, regardez la méthode d'intersection dans set, elle tend à être indulgente. Ici, j'essaie de croiser un ensemble vide avec une liste vide:

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

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

ne sont lancées qu'en cas de besoin:

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

bien sûr, il y a des cas où retourner vrai ou faux sur la réussite ou l'échec peut être bon. Mais il est très important d'être cohérent. La fonction doit toujours retourner le même type ou la même structure. Il est très déroutant d'avoir une fonction qui retourne une liste ou un booléen. Ou retourner le même type mais la signification de cette valeur peut être différente en cas d'erreur.

EDIT:

L'OP dit:

je veux retourner quelque chose pour indiquer les paramètres sont incorrects.

rien ne dit qu'il y a une erreur mieux qu'une exception. Si vous voulez indiquer que les paramètres sont incorrects, utilisez des exceptions et mettez un message d'erreur utile. Retourner un résultat dans ce cas est juste déroutant. Il pourrait y d'autres cas où vous souhaitez indiquer que rien ne s'est passé, mais il n'est pas une erreur. Par exemple, si vous avez une méthode qui supprime des entrées d'une table et l'entrée demandée pour la suppression n'existe pas. Dans ce cas, il pourrait être bien de simplement retourner vrai ou faux sur le succès ou l'échec. Il dépend de l'application et du comportement

50
répondu Nadia Alramli 2009-10-27 15:41:38

il serait préférable de soulever une exception que de retourner une valeur spéciale. C'est exactement pour cela que les exceptions ont été conçues, pour remplacer les codes d'erreur par un mécanisme de traitement des erreurs plus robuste et structuré.

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)

dans ce cas précis, bien que je laisse tomber les tests. Il n'y a rien de mal à recouper des listes vides, vraiment. Aussi lambda est en quelque sorte découragé ces jours-ci de préférence à des compréhensions de liste. Voir trouver l'intersection de deux listes? pour un couple de façons d'écrire ceci sans utiliser lambda .

19
répondu John Kugelman 2017-05-23 11:33:14

je voudrais retourner un n-uplet:

(Vrai, some_result)

(Faux, some_useful_response)

l'objet some_useful_response peut être utilisé pour gérer la condition de retour ou peut servir à afficher des informations de débogage.

NOTE : cette technique s'applique aux valeurs de retour de toute sorte. Il ne pas confondre avec exceptions .

à la réception, vous avez juste à déballer:

Code de Réponse = une_fonction(...)

cette technique s'applique au flux de commande" normal": il faut utiliser la fonctionnalité d'exception lorsque des entrées / traitements inattendus se produisent.

à noter aussi: cette technique aide à normaliser les retours de fonction. Le programmeur et l'utilisateur des fonctions savoir à quoi s'attendre .

AVERTISSEMENT: je viens d'une Erlang fond :-)

12
répondu jldupont 2009-10-27 15:08:57
Les Exceptions

sont certainement meilleures (et plus Pythoniques) que les retours de statut. Pour beaucoup plus sur ce point: Exceptions vs statut renvoie

11
répondu Ned Batchelder 2009-10-27 14:19:50

le cas général est de jeter des exceptions pour des circonstances exceptionnelles. Je souhaite que je pourrais me souvenir de la citation exacte (ou qui l'a dit), mais vous devriez vous efforcer pour les fonctions qui acceptent autant de valeurs et de types comme est raisonnable et maintenir un comportement très étroitement défini. C'est une variante de ce que Nadia parlait de . Considérez les usages suivants de votre fonction:

  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])

Je m'attendrais à ce que (5) lance une exception puisque passer False est une erreur de type. Le reste d'entre eux, cependant, font une sorte de sens, mais il dépend vraiment du contrat que la fonction déclare. Si intersect_two_lists étaient définis comme renvoyant le intersection de deux itérables , puis tout autre que (5) devrait fonctionner aussi longtemps que vous faites None une représentation valide du jeu vide . La mise en œuvre serait quelque chose comme:

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

j'écris habituellement des fonctions d'aide qui font respecter le contrat, quel qu'il soit, et je les appelle ensuite pour vérifier toutes les conditions préalables. Quelque chose comme:

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

vous pouvez aussi étendre ce concept et passer dans le "Politique comme un argument facultatif. Je ne conseillerais pas de le faire à moins que vous ne souhaitiez adopter Policy Based Design . Je voulais vous le dire au cas où vous n'auriez pas envisagé cette option.

si le contrat pour intersect_two_lists est qu'il n'accepte que deux paramètres list non vides, alors être explicite et jeter des exceptions si le contrat n'est pas respecté:

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

je pense que la morale de l'histoire est quoi que vous fassiez, faites-le systématiquement et soyez explicite . Personnellement, je suis généralement favorable à l'augmentation des exceptions chaque fois qu'un contrat est violé ou on me donne une valeur que je ne peux vraiment pas utiliser. Si les valeurs que je donne sont raisonnables, alors j'essaie de faire quelque chose de raisonnable en retour. Vous pouvez également lire la rubrique de la FAQ C++ sur les exceptions. Cette entrée donne vous un peu plus matière à réflexion sur les exceptions.

3
répondu D.Shawley 2017-05-23 12:03:09

pour collections (listes, sets, dicts, etc.) retourner une collection vide est le choix évident car il permet à la logique de votre site d'appel de rester à l'écart de la logique défensive. Plus explicitement, une collection vide est toujours une réponse parfaitement bonne d'une fonction à partir de laquelle vous attendez une collection, vous ne devez pas vérifier que le résultat est de tout autre type, et peut continuer votre logique d'affaires d'une manière propre.

pour les résultats de non-collecte Il ya plusieurs façons de traiter les retours conditionnels:

  1. comme de nombreuses réponses l'ont déjà expliqué, l'utilisation D'Exceptions est une façon de résoudre ce problème, et est idiomatique python. Cependant, je préfère ne pas utiliser d'Exceptions pour le flux de contrôle, car je trouve que cela crée une ambiguïté sur les intentions d'une Exception. Il faut plutôt prévoir des Exceptions dans des situations exceptionnelles réelles.
  2. une autre solution est de retourner None au lieu de votre résultat attendu, mais cela force un utilisateur à ajouter des contrôles défensifs partout dans leurs sites d'appel, brouillant la logique d'affaires réelle qu'ils essaient d'exécuter.
  3. une troisième façon consiste à utiliser un type de collection qui ne peut contenir qu'un seul élément (ou qui est explicitement vide). Ceci est appelé un optionnel et est ma méthode préférée parce qu'il vous permet de garder la logique de site d'appel propre. Cependant, python n'est intégré dans l'option type, donc j'utilise mon propre. Je l'ai édité dans une petite bibliothèque appelée optional.py si quelqu'un veut l'essayer. Vous pouvez l'installer en utilisant pip install optional.py . Je me félicite de commentaires, de demandes de fonctionnalités, et de contributions.
0
répondu Chad Befus 2018-09-26 21:33:34