web-dev-qa-db-fra.com

Python: Comment savoir quelles exceptions peuvent être levées à partir d'un appel de méthode

Existe-t-il un moyen de savoir (au moment du codage) à quelles exceptions s'attendre lors de l'exécution du code python? J'attrape la classe d'exception de base 90% du temps, car je ne sais pas quel type d'exception peut être levé (et ne me dites pas de lire la documentation. Plusieurs fois, une exception peut être propagée à partir de la documentation n'est pas mise à jour ou correcte). Existe-t-il un outil pour vérifier cela? (comme en lisant le code et les libs de python)?

67
GabiMe

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

Je ne suis pas au courant d'un outil qui vérifie les exceptions, mais vous pouvez créer votre propre outil correspondant à vos besoins (une bonne occasion de jouer un peu avec l'analyse statique).

Dans un premier temps, vous pouvez écrire une fonction qui construit un AST, trouve tous les nœuds Raise, puis essaie de déterminer les modèles courants d’exception (par exemple, en appelant directement un constructeur).

Soit x le programme suivant:

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

Construisez le AST en utilisant le paquetage compiler:

tree = compiler.parse(x)

Définissez ensuite une classe de visiteurs Raise:

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

Et parcourez les AST collectant les nœuds Raise:

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 des données, etc. Vous pouvez également en déduire que CallFunc(Name('IOError'), ...) "doit absolument signifier la levée de IOError", ce qui convient parfaitement pour des résultats pratiques rapides :)

20
Andrey Vlasovskikh

Vous ne devriez intercepter que des exceptions que vous allez gérer.

Catching toutes les exceptions par leurs types concrets est un non-sens. Vous devriez intercepter des exceptions spécifiques que vous pouvez et aurez manipulez. Pour les autres exceptions, vous pouvez écrire une capture générique qui capture "Exception de base", la connecte (utilisez la fonction str()) et termine votre programme (ou fait autre chose qui convient dans une situation critique).

Si vous voulez vraiment gérer les toutes exceptions et que vous êtes certain qu'aucune d'entre elles n'est fatale (par exemple, si vous exécutez le code dans un environnement en mode bac à sable), votre approche de capture de BaseException générique correspond à votre objectifs.

Vous pourriez également être intéressé par référence à une exception de langage , pas une référence pour la bibliothèque que vous utilisez.

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

De toute façon, vous devrez exécuter des tests, car même si la méthode permettant d’obtenir les exceptions par code source existait, cela ne vous donnerait aucune idée de la façon dont vous devriez gérer l’un de ces . Vous devriez peut-être afficher le message d'erreur "Le fichier needful.txt n'est pas trouvé!" quand vous attrapez IndexError? Seul test peut dire.

24
P Shved

Le bon outil pour résoudre ce problème est unittests. Si vous rencontrez des exceptions générées par du code réel que les unittests ne déclenchent pas, vous avez besoin de davantage d'unitests.

Considère ceci

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

canard peut être n'importe quel objet

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

Supposons maintenant que vous ayez un code comme celui-ci

arr[i] = get_something_from_database()

Si elle soulève une IndexError, vous ne savez pas si elle provient d’arr [i] ou de la profondeur de la fonction de base de données. généralement, peu importe l'endroit où l'exception s'est produite, mais quelque chose s'est mal passé et ce que vous vouliez arriver ne s'est pas produit.

Une technique pratique consiste à attraper et peut-être à relancer l'exception comme celle-ci

except Exception as e
    #inspect e, decide what to do
    raise
12
John La Rooy

Pour l’instant, personne n’a expliqué pourquoi il n’était pas possible de disposer d’une liste complète d’exceptions correctes à 100%. 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)

Désormais, apl peut déclencher toute exception soulevée par f. 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, une carte, une réduction, etc. est affecté.

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

4
viraptor

Je me suis heurté à cela lors de l'utilisation de socket, je voulais connaître toutes les conditions d'erreur auxquelles je me heurterais (donc plutôt que d'essayer de créer des erreurs et de déterminer le socket, je voulais juste une liste concise). Finalement, j'ai fini par grep'ing "/usr/lib64/python2.4/test/test_socket.py" pour "augmenter":

$ 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 assez concise d'erreurs. Bien entendu, cela ne fonctionne que cas par cas et dépend de la précision des tests (comme ils le sont habituellement). Sinon, vous devez capturer toutes les exceptions, les consigner, les disséquer et comprendre comment les gérer (ce qui, avec les tests unitaires, ne serait pas trop difficile).

3
Kurt

normalement, vous n’auriez besoin d’attraper l’exception que quelques lignes de code. Vous ne voudriez pas mettre toute votre fonction main dans la clause try except. pour chaque ligne, vous devez toujours maintenant (ou pouvoir facilement vérifier) ​​quel type d’exception peut être levée.

les docs ont une liste exhaustive des exceptions intégrées . n'essayez pas d'excepter les exceptions que vous n'attendiez pas, elles pourraient être gérées/attendues dans le code appelant.

edit : ce qui peut être jeté dépend évidemment de ce que vous faites! accéder à un élément aléatoire d'une séquence: IndexError, un élément aléatoire d'un dict: KeyError, etc.

Essayez simplement d’exécuter ces quelques lignes dans IDLE et de créer une exception. Mais unittest serait une meilleure solution, naturellement.

1
SilentGhost

J'ai trouvé informatif de deux manières. Le premier, lancez les instructions dans iPython, qui affichera le type d’exception.

n = 2
str = 'me '
str + 2
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Dans le second cas, nous nous contentons d'attraper trop et de l'améliorer. Incluez une expression try dans votre code et attrapez sauf Exception as err . Imprimez suffisamment de données pour savoir quelle exception a été levée. Au fur et à mesure que des exceptions sont levées, améliorez votre code en ajoutant une clause d'exception plus précise. Lorsque vous estimez que vous avez mis en cache toutes les exceptions pertinentes, supprimez celle tout compris. De toute façon, c'est une bonne chose à faire, car cela avale des erreurs de programmation.

try:
   so something
except Exception as err:
   print "Some message"
   print err.__class__
   print err
   exit(1)
0
Rahav