97 votes

Lever une exception ou renvoyer None 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 consiste à laisser l'exception et à la gérer dans le code de l'appelant, mais je pense qu'il est plus clair de gérer une exception de type FileNotFoundError qu'un IndexError . Ou est-il mal vu de relancer une exception portant un nom différent ?

0 votes

3 votes

Je penche pour la levée d'une exception afin d'être obligé de gérer l'exception dans la fonction appelante. Si j'oublie de vérifier si la sortie est None dans la fonction appelante, je pourrais avoir un bug latent. Si vous avez renvoyé None, la ligne suivante de la fonction appelante lèvera une AttributeError. Cependant, si la valeur renvoyée est ajoutée à un dictionnaire et que 100 appels de fonction dans un fichier source différent soulèvent une AttributeError, vous vous amuserez à chercher pourquoi cette valeur était None.

0 votes

En général, j'évite également les valeurs qui ont une signification particulière ou qui ont plusieurs signatures pour une même fonction (elle pourrait renvoyer une chaîne de caractères ou None).

103voto

Eevee Points 18333

C'est une question de sémantique. Que signifie foo = latestpdf(d) moyen ?

Est-il parfaitement raisonnable qu'il n'y ait pas de dernier fichier ? Alors bien sûr, il suffit de renvoyer None .

Vous attendez-vous à toujours trouver un fichier récent ? Levez une exception. Et oui, lever à nouveau une exception plus appropriée est tout à fait possible.

S'il s'agit d'une fonction générale censée s'appliquer à n'importe quel répertoire, j'opterais pour la première solution et renverrais l'information suivante None . Si le répertoire est, par exemple, destiné à être un répertoire de données spécifique contenant l'ensemble des fichiers connus d'une application, je soulèverais une exception.

3 votes

Un autre point à prendre en considération : Si l'on soulève une exception, un message peut être joint, mais on ne peut pas le faire lors d'un retour. None .

0 votes

En outre, la levée et le traitement des exceptions sont plus lents : stackoverflow.com/a/17359901/1116842

0 votes

@moi de l'ordre de microsecondes une différence qui n'est pas significative pour une fonction unique qui ne sera probablement pas appelée très souvent. (et ce test n'est même pas très bon ; le chemin d'exception construit un nouvel objet, alors que le chemin de retour ne le fait pas).

9voto

rh0dium Points 2771

Avant de répondre à votre question, j'aimerais faire quelques suggestions qui pourraient vous apporter une réponse.

  • Donnez toujours à vos fonctions un nom descriptif. latestpdf ne signifie pas grand-chose pour qui que ce soit, mais en regardant votre fonction latestpdf() obtient le dernier pdf. Je vous suggère de le nommer getLatestPdfFromFolder(folder) .

Dès que j'ai fait cela, j'ai compris ce qu'il fallait retourner S'il n'y a pas de pdf, il faut lever une exception. Mais attendez, il y en a d'autres

  • Les fonctions doivent être clairement définies. Puisqu'il n'est pas évident de savoir ce que somefuc est censé faire et qu'il n'est pas (apparemment) évident de savoir comment il est lié à l'obtention du dernier pdf, je suggérerais que vous le retiriez. 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 vous aidera !

5 votes

Ou get_latest_pdf_from_folder . En effet, Pep8 : "Les noms de fonctions doivent être en minuscules, les mots étant séparés par des traits de soulignement si nécessaire pour améliorer la lisibilité".

6voto

David Berger Points 5459

Je préfère généralement gérer les exceptions en interne (c'est-à-dire essayer/excepter à l'intérieur de la fonction appelée, en retournant éventuellement un None) parce que Python est dynamiquement typé. En général, je considère que c'est une question de jugement dans un sens ou dans l'autre, mais dans un langage à typage dynamique, il y a de petits facteurs qui font pencher la balance en faveur de ne pas passer l'exception à l'appelant :

  1. Toute personne appelant votre fonction n'est pas informée des exceptions qui peuvent être lancées. Cela devient un peu un art de savoir quel type d'exception vous recherchez (et les blocs génériques excepté doivent être évités).
  2. if val is None est un peu plus facile que except ComplicatedCustomExceptionThatHadToBeImportedFromSomeNameSpace . Sérieusement, je déteste devoir me rappeler de taper from django.core.exceptions import ObjectDoesNotExist au début de tous mes fichiers django, juste pour gérer un cas d'utilisation très courant. Dans un monde statiquement typé, laissez l'éditeur le faire pour vous.

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

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

qui, neuf fois sur dix, est ce que l'appelant verra si vous renvoyez un None non géré, ne vous embêtez pas.

(Tout ceci me fait souhaiter que les exceptions python aient la fonction cause par défaut, comme en java, qui vous permet de passer des exceptions dans de nouvelles exceptions, de sorte que vous pouvez relancer autant que vous voulez sans jamais perdre la source originale du problème).

0 votes

L'argument selon lequel les exceptions possibles ne sont pas définies et qu'il est donc difficile de les attraper est un argument très valable pour Python.

0 votes

Vous pouvez passer des exceptions dans de nouvelles exceptions (et cela est fait par défaut si vous levez une nouvelle exception pendant que vous traitez une exception existante) : except exc: raise NewException(...) from exc .

5voto

Asaf Points 45

Avec python 3.5 dactylographie :

L'exemple d'une fonction renvoyant None est le suivant :

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

et quand une exception sera soulevée :

def latestpdf(folder: str) -> str 

l'option 2 semble plus lisible et pythonique

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

2voto

Peter Points 4655

En général, je dirais qu'une exception doit être levée si quelque chose de catastrophique s'est produit et ne peut être récupéré (par exemple, votre fonction traite d'une ressource Internet à laquelle il est impossible de se connecter), et vous devez renvoyer None si votre fonction devrait vraiment renvoyer quelque chose mais que rien ne serait approprié (par exemple, "None" si votre fonction essaie de faire correspondre une sous-chaîne dans une chaîne de caractères).

0 votes

str.find est un exemple de ce comportement : Il renvoie -1 si la sous-chaîne n'a pas été trouvée. Il s'agit toutefois d'une traduction directe de l'implémentation en C. liste.index est un contre-exemple : Il soulève une ValueError si la valeur n'a pas été trouvée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X