Quelle exception dois-je lever sur les combinaisons d'arguments incorrectes / illégales en Python?

545

Je me demandais quelles étaient les meilleures pratiques pour indiquer des combinaisons d'arguments invalides en Python. Je suis tombé sur quelques situations où vous avez une fonction comme ça:

def import_to_orm(name, save=False, recurse=False):
    """
    :param name: Name of some external entity to import.
    :param save: Save the ORM object before returning.
    :param recurse: Attempt to import associated objects as well. Because you
        need the original object to have a key to relate to, save must be
        `True` for recurse to be `True`.
    :raise BadValueError: If `recurse and not save`.
    :return: The ORM object.
    """
    pass

Le seul inconvénient est que chaque paquet a le sien, généralement légèrement différent BadValueError. Je sais qu'il existe en Java java.lang.IllegalArgumentException- est-il bien entendu que tout le monde va créer son propre BadValueErrors en Python ou existe-t-il une autre méthode préférée?

cdleary
la source

Réponses:

609

Je voudrais simplement soulever ValueError , sauf si vous avez besoin d'une exception plus spécifique ..

def import_to_orm(name, save=False, recurse=False):
    if recurse and not save:
        raise ValueError("save must be True if recurse is True")

Il n'y a vraiment aucun intérêt à le faire class BadValueError(ValueError):pass- votre classe personnalisée est identique à ValueError , alors pourquoi ne pas l'utiliser?

dbr
la source
65
> "alors pourquoi ne pas l'utiliser?" - Spécificité. Peut-être que je veux attraper une couche externe "MyValueError", mais pas une / toutes "ValueError".
Kevin Little
7
Oui, donc une partie de la question de la spécificité est de savoir où d'autre ValueError est soulevée. Si la fonction appelée aime vos arguments mais appelle math.sqrt (-1) en interne, un appelant peut attraper ValueError s'attend à ce que ses arguments soient inappropriés. Peut-être que vous venez de vérifier le message dans ce cas ...
cdleary
3
Je ne suis pas sûr que cet argument soit valable: si quelqu'un appelle math.sqrt(-1), c'est une erreur de programmation qui doit être corrigée de toute façon. ValueErrorn'est pas destiné à être pris dans l'exécution normale du programme ou il en dériverait RuntimeError.
2015
2
Si l'erreur est sur le NOMBRE d'arguments, pour une fonction avec un nombre variable d'arguments ... par exemple une fonction où les arguments doivent être un nombre pair d'arguments, alors vous devez déclencher une TypeError, pour être cohérent. Et ne créez pas votre propre classe sauf si a) vous avez un cas d'utilisation ou b) vous exportez la bibliothèque pour qu'elle soit utilisée par d'autres. La fonctionnalité prématurée est la mort du code.
Erik Aronesty
104

J'hériterais de ValueError

class IllegalArgumentError(ValueError):
    pass

Il est parfois préférable de créer vos propres exceptions, mais héritez d'une exception intégrée, qui est aussi proche de ce que vous voulez que possible.

Si vous devez détecter cette erreur spécifique, il est utile d'avoir un nom.

Markus Jarderot
la source
26
Arrêtez d'écrire des cours et des exceptions personnalisées - pyvideo.org/video/880/stop-writing-classes
Hamish Grubijan
41
@HamishGrubijan cette vidéo est terrible. Quand quelqu'un a suggéré une bonne utilisation d'une classe, il a juste bêlé "N'utilisez pas de classes." Brillant. Les cours sont bons. Mais ne me croyez pas sur parole .
Rob Grant
12
@RobertGrant Non, vous ne comprenez pas. Cette vidéo ne parle pas vraiment de "ne pas utiliser de classes". Il s'agit de ne pas trop compliquer les choses.
RayLuo
16
@RayLuo, vous avez peut-être vérifié ce que dit la vidéo et converti en un message alternatif sensible et sensible, mais c'est ce que dit la vidéo, et c'est ce que quelqu'un qui n'a pas beaucoup d'expérience et de bon sens va repartir avec.
Rob Grant
4
@SamuelSantana comme je l'ai dit, chaque fois que quelqu'un lève la main et dit "et X?" où X était une bonne idée, il a juste dit: "ne faites pas une autre classe." Assez clair. Je suis d'accord que la clé est l'équilibre; le problème est que c'est trop vague pour vivre réellement par :-)
Rob Grant
18

Je pense que la meilleure façon de gérer cela est la façon dont python le gère lui-même. Python déclenche une TypeError. Par exemple:

$ python -c 'print(sum())'
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: sum expected at least 1 arguments, got 0

Notre développeur junior vient de trouver cette page dans une recherche Google pour "les arguments incorrects de l'exception python" et je suis surpris que la réponse évidente (pour moi) n'ait jamais été suggérée dans la décennie depuis que cette question a été posée.

J Bones
la source
8
Rien ne me surprend mais je conviens à 100% que TypeError est la bonne exception si le type est incorrect sur certains des arguments passés dans la fonction. Une ValueError conviendrait si les variables sont de type correct mais que leur contenu et leurs valeurs n'ont pas de sens.
user3504575
Je pense que c'est probablement pour les arguments manquants ou non appelés, alors que la question concerne les arguments qui sont donnés correctement, mais qui sont incorrects à un niveau d'abstraction plus élevé impliquant la valeur de l'argument donné. Mais comme je cherchais le premier, ayez quand même un vote positif.
Personne le
2
Comme @ user3504575 et @Nobody l'ont dit, TypeError est utilisé si les arguments ne correspondent pas à la signature de la fonction (mauvais nombre d'arguments positionnels, arguments de mot-clé avec le mauvais nom, mauvais type d'argument), mais une ValueError est utilisée lorsque l'appel de fonction correspond à la signature mais les valeurs d'argument ne sont pas valides (par exemple, appel int('a')). source
goodmami
Comme la question de l'OP faisait référence à des "combinaisons d'arguments invalides", il semble qu'une TypeError serait appropriée car ce serait un cas où la signature de fonction est essentiellement incorrecte pour les arguments passés.
J Bones
Votre exemple appelle sum()sans argument, ce qui est un TypeError, mais l'OP se préoccupait des combinaisons «illégales» de valeurs d'argument lorsque les types d'argument sont corrects. Dans ce cas, les deux saveet recursesont des bools, mais si recursec'est Truealors savene devrait pas l'être False. Ceci est un ValueError. Je suis d'accord qu'une certaine interprétation du titre de la question serait répondu par TypeError, mais pas pour l'exemple qui est présenté.
goodmami
11

Je viens surtout de voir le builtin ValueErrorutilisé dans cette situation.

Eli Courtwright
la source
8

Cela dépend du problème avec les arguments.

Si l'argument a le mauvais type, déclenchez une TypeError. Par exemple, lorsque vous obtenez une chaîne au lieu d'un de ces booléens.

if not isinstance(save, bool):
    raise TypeError(f"Argument save must be of type bool, not {type(save)}")

Notez cependant qu'en Python, nous effectuons rarement des vérifications comme celle-ci. Si l'argument est vraiment invalide, une fonction plus profonde fera probablement la plainte pour nous. Et si nous vérifions uniquement la valeur booléenne, peut-être qu'un utilisateur de code lui fournira plus tard une chaîne en sachant que les chaînes non vides sont toujours True. Cela pourrait lui sauver un casting.

Si les arguments ont des valeurs non valides, augmentez ValueError. Cela semble plus approprié dans votre cas:

if recurse and not save:
    raise ValueError("If recurse is True, save should be True too")

Ou dans ce cas spécifique, avoir une valeur True de recurse implique une valeur True de save. Étant donné que je considérerais cela comme une récupération après une erreur, vous pouvez également vous plaindre dans le journal.

if recurse and not save:
    logging.warning("Bad arguments in import_to_orm() - if recurse is True, so should save be")
    save = True
Gloweye
la source
Je pense que c'est la réponse la plus précise. C'est évidemment sous-estimé (7 votes jusqu'à présent, y compris le mien).
Siu Ching Pong -Asuka Kenji-
-1

Je ne suis pas sûr d'être d'accord avec l'héritage de ValueError- mon interprétation de la documentation est que celle ValueError-ci n'est censée être générée que par les buildins ... l'hériter ou la générer vous-même semble incorrect.

Déclenché lorsqu'une opération ou une fonction intégrée reçoit un argument qui a le bon type mais une valeur inappropriée, et la situation n'est pas décrite par une exception plus précise telle que IndexError.

- Documentation ValueError

cdleary
la source
Comparez google.com/codesearch?q=lang:python+class \ + \ w Erreur (([^ E] \ w * | E [^ x] \ w )): avec google.com/codesearch?q=lang: python + class \ + \ w * Erreur (exception):
Markus Jarderot
13
Ce texte de présentation signifie simplement que les éléments intégrés le soulèvent, et non que seuls les éléments intégrés peuvent le relever. Il ne serait pas tout à fait approprié dans ce cas que la documentation Python parle de ce que les bibliothèques externes soulèvent.
Ignacio Vazquez-Abrams
5
Chaque morceau de logiciel Python que j'ai jamais vu a utilisé ValueErrorpour ce genre de chose, donc je pense que vous essayez d'en lire trop dans la documentation.
James Bennett
6
Euh, si nous allons utiliser les recherches de code Google pour argumenter ceci: google.com/codesearch?q=lang%3Apython+raise%5C+ValueError # 66300 cas de génération de ValueError, y compris Zope, xen, Django, Mozilla (et cela vient de la première page de résultats). Si une exception intégrée convient, utilisez-la ..
dbr
7
Comme indiqué, la documentation est ambiguë. Il aurait dû être écrit comme "Augmenté lorsqu'une opération intégrée ou une fonction intégrée reçoit" ou comme "Augmenté lorsqu'une fonction ou une opération intégrée reçoit". Bien sûr, quelle que soit l'intention d'origine, la pratique actuelle l'a emporté (comme le souligne @dbr). Il devrait donc être réécrit comme la deuxième variante.
Éponyme
-1

Acceptez la suggestion de Markus de lever votre propre exception, mais le texte de l'exception devrait clarifier que le problème se trouve dans la liste d'arguments, pas dans les valeurs d'argument individuelles. Je proposerais:

class BadCallError(ValueError):
    pass

Utilisé lorsque des arguments de mots clés sont manquants qui étaient requis pour l'appel spécifique, ou que les valeurs d'argument sont valides individuellement mais incohérentes les unes avec les autres. ValueErroraurait toujours raison quand un argument spécifique est de type correct mais hors de portée.

Cela ne devrait-il pas être une exception standard en Python?

En général, j'aimerais que le style Python soit un peu plus net pour distinguer les mauvaises entrées d'une fonction (faute de l'appelant) des mauvais résultats au sein de la fonction (ma faute). Il peut donc y avoir également une erreur BadArgumentError pour distinguer les erreurs de valeur dans les arguments des erreurs de valeur dans les sections locales.

BobHy
la source
Je relancerais KeyErrorpour un mot clé introuvable (car un mot clé explicite manquant est sémantiquement identique à un **kwargsdict qui manque cette clé).
cowbert