web-dev-qa-db-fra.com

Raise exception vs. return Aucun dans les fonctions?

Quelle est la meilleure pratique dans une fonction définie par l'utilisateur en Python: raise une exception ou return None? Par exemple, j'ai une fonction qui trouve le fichier le plus récent dans un dossier.

def latestpdf(folder):
    # list the files and sort them
    try:
        latest = files[-1]
    except IndexError:
        # Folder is empty.
        return None  # One possibility
        raise FileNotFoundError()  # Alternative
    else:
        return somefunc(latest)  # In my case, somefunc parses the filename

Une autre option est de laisser l'exception et de la gérer dans le code de l'appelant, mais je suppose qu'il est plus facile de traiter un FileNotFoundError qu'un IndexError. Ou est-ce une mauvaise forme de relancer une exception avec un nom différent?

67
parcydarks

C'est vraiment une question de sémantique. Qu'est-ce que foo = latestpdf(d) signifie ?

Est-il parfaitement raisonnable de ne pas avoir le dernier fichier? Alors bien sûr, retournez None.

Vous attendez-vous toujours à trouver le dernier fichier? Lève une exception. Et oui, relancer une exception plus appropriée est acceptable.

S'il s'agit simplement d'une fonction générale supposée s'appliquer à n'importe quel répertoire, je referais la première et renverrais None. Si le répertoire est, par exemple, censé être un répertoire de données spécifique contenant le jeu de fichiers connu d'une application, je déclencherais une exception.

78
Eevee

Je voudrais faire quelques suggestions avant de répondre à votre question car cela pourrait répondre à la question pour vous.

  • Nommez toujours vos fonctions de manière descriptive. latestpdf signifie très peu pour quiconque mais examiner votre fonction latestpdf() obtient le dernier fichier PDF. Je suggérerais que vous le nommiez getLatestPdfFromFolder(folder).

Dès que j'ai fait cela, il est devenu clair ce qu'il devrait retourner. S'il n'y a pas de pdf, une exception est levée. Mais attendez là plus ..

  • Gardez les fonctions clairement définies. Étant donné que ce que somefuc est censé faire et que son rapport avec l'obtention du dernier fichier PDF n'est pas évident (apparemment), je vous suggère de le transférer. Cela rend le code beaucoup plus lisible.

for folder in folders:
   try:
       latest = getLatestPdfFromFolder(folder)
       results = somefuc(latest)
   except IOError: pass

J'espère que cela t'aides!

7
rh0dium

Je préfère généralement gérer les exceptions en interne (c’est-à-dire essayer/sauf dans la fonction appelée, en renvoyant éventuellement la valeur None) car python est typé de manière dynamique. En général, j’estime qu’il s’agit d’un jugement, d’une manière ou d’une autre, mais dans un langage typé de manière dynamique, il existe de petits facteurs qui font pencher la balance en faveur du refus de transmettre l’exception à l’appelant:

  1. Toute personne appelant votre fonction n'est pas informée des exceptions pouvant être levées. Cela devient un peu une forme d'art de savoir quel type d'exception vous recherchez (et les génériques sauf les blocs doivent être évités).
  2. if val is None est un peu plus facile que except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace. Sérieusement, je déteste avoir à oublier de taper from Django.core.exceptions import ObjectDoesNotExist en haut de tous mes fichiers Django uniquement pour gérer un cas d'utilisation très courant. Dans un monde statiquement typé, laissez l'éditeur le faire pour vous.

Honnêtement, cependant, c'est toujours un jugement, et la situation que vous décrivez, où la fonction appelée reçoit une erreur qu'il ne peut pas aider, est une excellente raison de relancer une exception significative. Vous avez exactement la bonne idée, mais à moins que vous ne soyez une exception, vous obtiendrez des informations plus utiles dans une trace de pile.

AttributeError: 'NoneType' object has no attribute 'foo'

ce que l’appelant verra, neuf fois sur dix, si vous renvoyez un None non pris en charge, ne vous embêtez pas.

(Tout cela me fait souhaiter que les exceptions python aient les attributs cause par défaut, comme en Java, ce qui vous permet de passer des exceptions à de nouvelles exceptions afin que vous puissiez rediffuser tout ce que vous voulez et ne jamais perdre la source d'origine du problème.)

4
David Berger

En général, je dirais qu’une exception devrait être levée en cas de catastrophe irrécupérable (c’est-à-dire que votre fonction traite certaines ressources Internet qui ne peuvent pas être connectées) et vous devez renvoyer None si votre fonction doit réellement renvoyer quelque chose. mais rien ne conviendrait de renvoyer (c'est-à-dire "None" si votre fonction tente de faire correspondre une chaîne dans une chaîne, par exemple).

2
user130076

avec python 3.5's en tapant :

exemple de fonction lors du retour Aucun sera:

def latestpdf(folder: str) -> Union[str, None]

et quand levant une exception sera:

def latestpdf(folder: str) -> str 

l'option 2 semble plus lisible et Pythonic

(+ option pour ajouter un commentaire à l'exception comme indiqué précédemment.)

0
Asaf