110 votes

Comment puis-je savoir quelles exceptions peuvent être levées par un appel de méthode ?

Existe-t-il un moyen de savoir (au moment du codage) à quelles exceptions il faut s'attendre lors de l'exécution du code python ?

Je finis par attraper la classe Exception de base dans 90 % des cas, car je ne sais pas quel type d'exception peut être déclenché (la lecture de la documentation ne m'aide pas toujours, car une exception peut souvent être propagée à partir de la profondeur). Et souvent, la documentation n'est pas mise à jour ou correcte).

Existe-t-il un outil permettant de vérifier cela (par exemple en lisant le code et les librairies Python) ?

3 votes

Gardez à l'esprit que dans Python < 2.6, vous pouvez raise ainsi que les chaînes de caractères, pas seulement BaseException sous-classes. Donc si vous faites appel à du code de bibliothèque qui est hors de votre contrôle, même except Exception n'est pas suffisant, puisqu'il ne permet pas d'attraper les exceptions relatives aux chaînes de caractères. Comme d'autres l'ont souligné, vous vous trompez de cible.

0 votes

Je ne le savais pas. Je pensais que sauf Exception : attrape presque tout.

3 votes

except Exception fonctionne bien pour attraper les exceptions de chaîne dans Python 2.6 et plus.

26voto

Pavel Shved Points 34706

Vous ne devez attraper que les exceptions que vous allez gérer.

Attraper toutes les exceptions par leurs types concrets est un non-sens. Vous devriez attraper les exceptions spécifiques que vous puede y sera poignée. Pour d'autres exceptions, vous pouvez écrire un catch générique qui attrape l'"exception de base", l'enregistre (en utilisant l'option str() ) et termine votre programme (ou fait quelque chose d'autre qui est approprié dans une situation de crash).

Si tu veux vraiment gérer tous et que vous êtes sûr qu'aucune d'entre elles n'est fatale (par exemple, si vous exécutez le code dans une sorte d'environnement sandbox), votre approche consistant à attraper les BaseException génériques correspond à vos objectifs.

Vous pourriez également être intéressé par référence d'exception linguistique et non une référence pour la bibliothèque que vous utilisez.

Si la référence de la bibliothèque est vraiment mauvaise et qu'elle ne relance pas ses propres exceptions lorsqu'elle attrape celles du système, la seule approche utile est d'exécuter des tests (peut-être l'ajouter à la suite de tests, car si quelque chose n'est pas documenté, cela peut changer !) Supprimez un fichier crucial pour votre code et vérifiez quelle exception est levée. Fournissez trop de données et vérifiez quelle erreur cela produit.

Vous devrez de toute façon effectuer des tests, puisque, même si la méthode permettant d'obtenir les exceptions par le code source existait, elle ne vous donnerait aucune idée de la manière dont vous devez traiter ces exceptions. . Peut-être devriez-vous afficher le message d'erreur "Le fichier needful.txt n'est pas trouvé !" lorsque vous attrapez IndexError ? Seul le test peut le dire.

39 votes

Bien sûr, mais comment décider des exceptions à gérer si l'on ne sait pas ce qui peut être lancé ?

0 votes

@bugspy.net , j'ai corrigé ma réponse pour refléter cette question.

0 votes

Peut-être qu'il est temps pour un analyseur de code source qui peut trouver cela ? Cela ne devrait pas être trop difficile à développer, je pense.

25voto

Andrey Vlasovskikh Points 6903

Je suppose qu'une solution ne pourrait être qu'imprécise en raison de l'absence de règles de typage statique.

Je ne connais pas d'outil qui vérifie les exceptions, mais vous pourriez créer votre propre outil en fonction de vos besoins (une bonne occasion de jouer un peu avec l'analyse statique).

Comme première tentative, vous pourriez écrire une fonction qui construit un AST, trouve tous les éléments de l'AST. Raise et essaie ensuite de déterminer les modèles courants de levée d'exceptions (par exemple, l'appel direct d'un constructeur).

Soit x soit le programme suivant :

x = '''\
if f(x):
    raise IOError(errno.ENOENT, 'not found')
else:
    e = g(x)
    raise e
'''

Construire l'AST en utilisant le compiler paquet :

tree = compiler.parse(x)

Définissez alors une Raise classe de visiteurs :

class RaiseVisitor(object):
    def __init__(self):
        self.nodes = []
    def visitRaise(self, n):
        self.nodes.append(n)

Et marcher sur l'AST en collectant Raise des nœuds :

v = RaiseVisitor()
compiler.walk(tree, v)

>>> print v.nodes
[
    Raise(
        CallFunc(
            Name('IOError'),
            [Getattr(Name('errno'), 'ENOENT'), Const('not found')],
            None, None),
        None, None),
    Raise(Name('e'), None, None),
]

Vous pouvez continuer en résolvant les symboles en utilisant les tables de symboles du compilateur, en analysant les dépendances de données, etc. Ou vous pouvez simplement déduire que CallFunc(Name('IOError'), ...) "devrait définitivement signifier l'augmentation IOError ", ce qui est tout à fait correct pour des résultats pratiques rapides :)

0 votes

Merci pour cette réponse intéressante. Je n'ai pas compris pourquoi je devais chercher autre chose que tous les nœuds de relance. Pourquoi devrais-je "résoudre les symboles en utilisant les tables de symboles du compilateur, analyser les dépendances des données" ? La seule façon de lever une exception n'est-elle pas d'utiliser raise() ?

1 votes

Compte tenu de la v.nodes valeur ci-dessus, vous ne pouvez pas vraiment dire, ce que la chose est Name('IOError') ou Name('e') . Vous ne savez pas quelle(s) valeur(s) ces IOError y e peuvent pointer, car ce sont des variables dites libres. Même si leur contexte de liaison était connu (c'est ici que les tables de symboles entrent en jeu), vous devriez effectuer une sorte d'analyse de dépendance des données pour déduire leurs valeurs exactes (cela devrait être difficile en Python).

0 votes

Comme vous êtes à la recherche d'une solution semi-automatique pratique, une liste de ['IOError(errno.ENOENT, "not found")', 'e'] affiché à l'utilisateur est très bien. Mais vous ne pouvez pas déduire les classes réelles des valeurs des variables représentées par des chaînes de caractères :) (désolé pour le repostage)

17voto

gnibbler Points 103484

L'outil correct pour résoudre ce problème est unittests. Si vous avez des exceptions soulevées par du code réel que les unittests ne soulèvent pas, alors vous avez besoin de plus de unittests.

Considérez ceci

def f(duck):
    try:
        duck.quack()
    except ??? could be anything

le canard peut être n'importe quel objet

Il est évident que vous pouvez avoir un AttributeError si le canard ne fait pas coin-coin, un TypeError si le canard a un coin-coin mais il n'est pas appelable. Vous n'avez aucune idée de ce que duck.quack() pourrait cependant soulever, peut-être même un DuckError ou quelque chose comme ça

Supposons maintenant que vous ayez un code comme celui-ci

arr[i] = get_something_from_database()

Si elle soulève un IndexError vous ne savez pas si elle provient de arr[i] ou de l'intérieur de la fonction de base de données. en général, l'endroit où l'exception s'est produite n'est pas très important, mais plutôt le fait que quelque chose s'est mal passé et que ce que vous vouliez ne s'est pas produit.

Une technique pratique est d'attraper et peut-être de relancer l'exception comme ceci

except Exception as e
    #inspect e, decide what to do
    raise

0 votes

Pourquoi l'attraper si c'est pour le "re-relancer" ?

0 votes

Tu ne le fais pas. ont pour la relancer, c'est ce que le commentaire était censé indiquer.

3 votes

Vous pouvez également choisir d'enregistrer l'exception quelque part, puis de relancer.

9voto

viraptor Points 12779

Personne n'a expliqué jusqu'à présent pourquoi il n'est pas possible d'avoir une liste d'exceptions complète et 100% correcte, alors j'ai pensé que cela valait la peine d'être commenté. L'une des raisons est une fonction de première classe. Disons que vous avez une fonction comme celle-ci :

def apl(f,arg):
   return f(arg)

Maintenant apl peut soulever toute exception que f soulève. Bien qu'il n'y ait pas beaucoup de fonctions de ce type dans la bibliothèque principale, tout ce qui utilise la compréhension de liste avec des filtres personnalisés, map, reduce, etc. est affecté.

La documentation et les analyseurs de sources sont les seules sources "sérieuses" d'information ici. Gardez à l'esprit ce qu'ils ne peuvent pas faire.

5voto

Kurt Points 2018

J'ai rencontré ce problème lors de l'utilisation de socket, je voulais trouver toutes les conditions d'erreur auxquelles je pouvais être confronté (donc plutôt que d'essayer de créer des erreurs et de comprendre ce que fait socket, je voulais juste une liste concise). Finalement, j'ai fini par rechercher "raise" dans "/usr/lib64/python2.4/test/test_socket.py" :

$ grep raise test_socket.py
Any exceptions raised by the clients during their tests
        raise TypeError, "test_func must be a callable function"
    raise NotImplementedError, "clientSetUp must be implemented."
    def raise_error(*args, **kwargs):
        raise socket.error
    def raise_herror(*args, **kwargs):
        raise socket.herror
    def raise_gaierror(*args, **kwargs):
        raise socket.gaierror
    self.failUnlessRaises(socket.error, raise_error,
    self.failUnlessRaises(socket.error, raise_herror,
    self.failUnlessRaises(socket.error, raise_gaierror,
        raise socket.error
    # Check that setting it to an invalid value raises ValueError
    # Check that setting it to an invalid type raises TypeError
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,
    def raise_timeout(*args, **kwargs):
    self.failUnlessRaises(socket.timeout, raise_timeout,

Ce qui est une liste d'erreurs assez concise. Bien sûr, cela ne fonctionne qu'au cas par cas et dépend de l'exactitude des tests (ce qui est généralement le cas). Sinon, il faut attraper toutes les exceptions, les enregistrer, les disséquer et trouver comment les gérer (ce qui n'est pas si difficile avec les tests unitaires).

4 votes

Cela renforce mon argument selon lequel la gestion des exceptions en Python est très problématique, si nous devons utiliser grep ou des analyseurs de sources pour traiter quelque chose d'aussi basique (ce qui, par exemple, existe en Java depuis le premier jour). Parfois, la verbosité est une bonne chose. Java est verbeux mais au moins il n'y a pas de mauvaises surprises).

0 votes

@GabiMe, Ce n'est pas comme si cette capacité (ou le typage statique en général) était une solution miracle pour éviter tous les bugs. Java est complet de mauvaises surprises. C'est pourquoi Eclipse se plante régulièrement.

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