185 votes

Utilisation de 'try' vs. 'if' en Python

Y a-t-il une logique pour décider lequel des try ou if constructeurs à utiliser, lors du test d'une variable pour avoir une valeur ?

Par exemple, il y a une fonction qui retourne soit une liste soit ne retourne pas de valeur. Je veux vérifier le résultat avant de le traiter. Lequel des suivants serait plus préférable et pourquoi ?

résultat = fonction();
if (résultat):
    pour r in résultat:
        # Traiter les items

ou

résultat = fonction();
try:
    for r in résultat:
        # Traiter les items
except TypeError:
    pass;

Discussion connexe:

Vérifier l'existence d'un membre en Python

0 votes

Gardez à l'esprit que si votre bloc d'éléments #process est susceptible de générer une TypeError, vous devez l'encadrer dans un autre bloc try: except:. Pour cet exemple spécifique, je me contenterais d'utiliser un if:

305voto

Tim Pietzcker Points 146308

On entend souvent dire que Python encourage le style EAFP ("il est plus facile de demander pardon que la permission") plutôt que le style LBYL ("regarder avant de sauter"). Pour moi, c'est une question d'efficacité et de lisibilité.

Dans votre exemple (disons que au lieu de renvoyer une liste ou une chaîne vide, la fonction renvoie une liste ou None), si vous vous attendez à ce que 99 % du temps résultat contienne réellement quelque chose d'itérable, j'utiliserais l'approche try/except. Ce sera plus rapide si les exceptions sont vraiment exceptionnelles. Si résultat est None plus de 50 % du temps, alors utiliser if est probablement mieux.

Pour étayer cela avec quelques mesures:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # pas de vérification d'erreur
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Ainsi, tandis qu'une instruction if vous coûte toujours, il est presque gratuit de mettre en place un bloc try/except. Mais lorsque qu'une Exception survient réellement, le coût est beaucoup plus élevé.

Moralité:

  • Il est parfaitement OK (et "pythonic") d'utiliser try/except pour le contrôle du flux,
  • mais cela a le plus de sens lorsque les Exceptions sont réellement exceptionnelles.

D'après la documentation Python:

EAFP

Il est plus facile de demander pardon que la permission. Ce style de codage Python courant suppose l'existence de clés ou attributs valides et capture les exceptions si l'hypothèse est fausse. Ce style propre et rapide est caractérisé par la présence de nombreuses instructions try et except. La technique contraste avec le style LBYL commun à de nombreux autres langages comme le C.

1 votes

Merci. Je vois que dans ce cas, la raison peut être l'attente du résultat.

7 votes

.... et c'est une (parmi tant d'autres) raisons pour lesquelles il est si difficile de réaliser un JIT d'optimisation réel pour Python. Comme le prouve récemment en version bêta LuaJIT 2, les langages dynamiques peuvent être vraiment, vraiment rapides; mais cela dépend fortement de la conception initiale du langage et du style qu'elle encourage. (sur une note connexe, la conception du langage est la raison pour laquelle même les meilleurs JITs JavaScript ne peuvent pas rivaliser avec LuaJIT 1, sans parler du 2)

0 votes

@Javier savez-vous quelque chose que je pourrais lire sur les différences de conception qui affectent le bénéfice potentiel des JITs?

15voto

Brandon Points 1968

Votre fonction ne devrait pas retourner des types mixtes (c'est-à-dire une liste ou une chaîne vide). Elle devrait renvoyer une liste de valeurs ou simplement une liste vide. Ainsi, vous n'auriez pas besoin de tester quoi que ce soit, c'est-à-dire que votre code se résume à :

for r in function():
    # traiter les éléments

3 votes

Je suis tout à fait d'accord avec vous sur ce point; cependant, la fonction ne m'appartient pas, et je l'utilise simplement.

2 votes

@artdanil: Ainsi, vous pourriez envelopper cette fonction dans une fonction que vous écrivez qui fonctionne comme celle à laquelle pense Brandon Corfman.

31 votes

Ce qui soulève la question : le wrapper doit-il utiliser une condition if ou un bloc try pour gérer le cas où l'objet n'est pas itérable ?

13voto

tzot Points 32224

Veuillez ignorer ma solution si le code que je fournis n'est pas évident à première vue et que vous devez lire l'explication après l'exemple de code.

Puis-je supposer que le "aucune valeur retournée" signifie que la valeur de retour est None? Si oui, ou si le "aucune valeur" est fausse du point de vue booléen, vous pouvez faire ce qui suit, puisque votre code traite essentiellement "aucune valeur" comme "ne pas itérer" :

for r in function() or ():
    # process items

Si function() renvoie quelque chose qui n'est pas Vrai, vous itérez sur le tuple vide, c'est-à-dire que vous n'exécutez aucune itération. C'est essentiellement LBYL.

6voto

Managu Points 5694

De manière générale, l'impression que j'ai eue est que les exceptions devraient être réservées aux circonstances exceptionnelles. Si le résultat est censé ne jamais être vide (mais pourrait l'être, si, par exemple, un disque a planté, etc.), la deuxième approche a du sens. Si, en revanche, un résultat vide est parfaitement normal dans des conditions normales, il est plus logique de le tester avec une instruction if.

J'avais à l'esprit le scénario (plus courant) :

# garder les comptes d'accès pour différents fichiers
file_counts={}
...
# obtenu un nom de fichier d'une manière ou d'une autre
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

au lieu de l'équivalent :

...
essayer :
    file_counts[filename]+=1
sauf KeyError :
    file_counts[filename]=1

0 votes

Ceci est un exemple de la différence entre les approches que Tim Pietzcker mentionne : Le premier est LBYL, le deuxième est EAFP

0 votes

Juste une petite note que ++ ne fonctionne pas en python, utilisez += 1 à la place.

4voto

Dave Kirby Points 12310

Votre deuxième exemple est cassé - le code ne lancera jamais d'exception TypeError car vous pouvez itérer à travers les deux chaînes et listes. Itérer à travers une chaîne ou liste vide est également valide - il exécutera le corps de la boucle zéro fois.

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