Question

Dans général , supposons que vous utilisiez une méthode comme celle décrite 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, lorsque des erreurs sont détectées, je ne souhaite pas renvoyer la liste vide dans ce cas car cela aurait pu être la vraie réponse à cet appel de méthode spécifique, je souhaite renvoyer quelque chose pour indiquer que les paramètres étaient incorrects. J'ai donc renvoyé Faux en cas d'erreur dans ce cas, ainsi qu'une liste (vide ou non).

Ma question est la suivante: quelle est la meilleure pratique dans des domaines comme celui-ci, et pas seulement pour les listes? Renvoyez ce que je veux et assurez-vous de le documenter pour qu'un utilisateur puisse le lire. :-) Que font la plupart d'entre vous:

  1. Si en cas de succès, vous deviez renvoyer Vrai ou Faux et vous avez une erreur?
  2. Si en cas de succès, vous deviez renvoyer une liste et vous attrapiez une erreur?
  3. Si en cas de succès, vous étiez censé renvoyer un descripteur de fichier et que vous attrapiez une erreur?
  4. et cetera
Était-ce utile?

La solution

Tout d'abord, quoi que vous fassiez, vous ne recevrez pas de résultat ni de message d'erreur. C'est un très mauvais moyen de gérer les erreurs et vous causera des maux de tête sans fin. Si vous devez indiquer une erreur, déclenchez toujours une exception .

J'ai tendance à éviter les erreurs, sauf si cela est nécessaire. Dans votre exemple, une erreur n'est pas vraiment nécessaire. Intersection d'une liste vide avec une autre non vide n'est pas une erreur. Le résultat est juste une liste vide et c'est correct. Mais disons que vous voulez gérer d'autres cas. Par exemple, si la méthode a obtenu un type de non-liste. Dans ce cas, il est préférable de lever une exception. Les exceptions ne sont pas à craindre.

Mon conseil est de consulter la bibliothèque Python pour des fonctions similaires et de voir comment Python traite ces cas particuliers. Par exemple, jetez un coup d'œil à la méthode d'intersection dans le jeu, elle a tendance à pardonner. 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 généré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 existe des cas où il peut être bon de renvoyer Vrai ou Faux en cas de succès ou d’échec. Mais il est très important d'être cohérent. La fonction doit toujours renvoyer le même type ou la même structure. Il est très déroutant d’avoir une fonction qui pourrait renvoyer une liste ou un booléen. Ou renvoyez le même type mais la signification de cette valeur peut être différente en cas d'erreur.

EDIT:

Le PO dit:

  

Je veux retourner quelque chose pour indiquer   les paramètres étaient incorrects.

Rien ne dit qu'il y a une erreur mieux qu'une exception. Si vous souhaitez indiquer que les paramètres sont incorrects, utilisez des exceptions et mettez un message d'erreur utile. Renvoyer un résultat dans ce cas est simplement déroutant. Il peut y avoir d’autres cas où vous souhaitez indiquer que rien ne s’est produit mais ce n’est pas une erreur. Par exemple, si vous avez une méthode qui supprime les entrées d'une table et que l'entrée demandée pour suppression n'existe pas. Dans ce cas, il pourrait être correct de simplement renvoyer Vrai ou Faux en cas de succès ou d’échec. Cela dépend de l'application et du comportement souhaité

Autres conseils

Il serait préférable de générer une exception plutôt que de renvoyer 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 plus robuste et structuré de gestion des erreurs.

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, je ne ferais probablement que laisser tomber les tests. Il n'y a rien de mal à croiser des listes vides, vraiment. De plus, lambda est en quelque sorte découragé de nos jours de préférence à la liste des compréhensions. Voir Rechercher une intersection de deux listes? pour connaître deux manières de l'écrire sans utiliser < code> lambda .

je voudrais retourner un tuple:

  

(vrai, un_résultat)

     

(False, 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.

REMARQUE : cette technique s'applique à toutes les valeurs de retour . Il ne faut pas se tromper avec cas d'exception .

Du côté du destinataire, il vous suffit de décompresser:

  

Code, Response = some_function (...)

Cette technique s’applique pour le " normal " flux de contrôle: il faut utiliser la fonctionnalité d'exception lorsque des entrées / traitements inattendus se produisent.

À noter également: cette technique permet de normaliser les retours de fonction. Le programmeur et l'utilisateur des fonctions savent à quoi s'attendre .

AVERTISSEMENT: je viens d’un fond d’Erlang: -)

Les exceptions sont définitivement meilleures (et plus pythoniques) que les retours d’état. Pour plus d'informations à ce sujet: Exceptions et retours d'état

Le cas général est de lever des exceptions pour des circonstances exceptionnelles. Je souhaite pouvoir me rappeler la citation exacte (ou qui l'a dit), mais vous devez rechercher des fonctions acceptant autant de valeurs et de types que ce qui est raisonnable et maintenir un comportement défini de manière très étroite. Ceci est une variante de ce que Nadia parlait de . Considérez les utilisations suivantes de votre fonction:

  1. intersect_two_lists (Aucun, Aucun)
  2. intersect_two_lists ([], ())
  3. intersect_two_lists ('12 ',' 23 ')
  4. intersect_two_lists ([1, 2], {1: 'un', 2: 'deux'}})
  5. intersect_two_lists (False, [1])
  6. intersect_two_lists (aucun, [1])

Je m'attendrais à ce que (5) lève une exception, car transmettre False est une erreur de type. Les autres ont toutefois un sens, mais cela dépend vraiment du contrat stipulé par la fonction. Si intersect_two_lists était défini comme renvoyant l'intersection de deux iterables , alors tout ce qui est différent de (5) devrait fonctionner aussi longtemps que vous faites de Aucun une représentation valide de l'ensemble vide . L'implémentation 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'assistance qui imposent le contrat, puis les appelle 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 également étendre ce concept et transmettre la "politique" en tant qu'argument facultatif. Je ne vous conseillerais de le faire que si vous souhaitez adopter la conception basée sur des règles . Je voulais le mentionner au cas où vous n'auriez pas envisagé cette option auparavant.

Si le contrat pour intersect_two_lists est qu'il n'accepte que deux paramètres list non vides, alors soyez explicite et envoyez des exceptions si le contrat est rompu:

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 régulièrement et soyez explicite . Personnellement, je suis généralement favorable à la création d'exceptions chaque fois qu'un contrat est violé ou que l'on me donne une valeur que je ne peux vraiment pas utiliser. Si les valeurs qui me sont données sont raisonnables, j'essaie alors de faire quelque chose de raisonnable en retour. Vous voudrez peut-être aussi lire la entrée de la FAQ Lite C ++ sur les exceptions. Cette entrée vous donne plus de matière à réflexion sur les exceptions.

Pour les collections (listes, ensembles, dessins, etc.), renvoyer 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 excellente réponse d'une fonction à partir de laquelle vous attendez une collection. Vous n'avez pas à vérifier que le résultat est d'un autre type et pouvez continuer votre logique métier de manière propre.

Pour les résultats non liés à la collecte, il existe plusieurs façons de gérer les retours conditionnels:

  1. Comme de nombreuses réponses l'ont déjà expliqué, utiliser Exceptions est un moyen de résoudre ce problème et constitue un python idiomatique. Cependant, ma préférence est de ne pas utiliser les exceptions pour le contrôle du flux, car cela crée une ambiguïté sur les intentions d'une exception. Au lieu de cela, déclenchez des exceptions dans des situations exceptionnelles réelles.
  2. Une autre solution consiste à renvoyer Aucun au lieu du résultat attendu, mais cela oblige l'utilisateur à ajouter des contrôles défensifs partout dans ses sites d'appels, en masquant la logique métier qu'il est en train de masquer. en essayant d'exécuter réellement.
  3. Une troisième façon consiste à utiliser un type de collection capable uniquement de contenir un seul élément (ou d'être explicitement vide). Ceci est appelé une option et est ma méthode préférée car elle vous permet de garder une logique de site d'appel propre. Cependant, python n'a pas de type facultatif intégré, aussi j'utilise le mien. Je l'ai publiée dans une petite bibliothèque appelée optional.py si quelqu'un veut essayer. Vous pouvez l’installer à l’aide de pip install optional.py . Je souhaite la bienvenue aux commentaires, demandes de fonctionnalités et contributions.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top