Python: Skąd mam wiedzieć, które wyjątki mogą być generowane z wywołania metody


89

Czy jest sposób, aby wiedzieć (w czasie kodowania), jakich wyjątków można się spodziewać podczas wykonywania kodu w Pythonie? W końcu łapię podstawową klasę wyjątków w 90% przypadków, ponieważ nie wiem, który typ wyjątku może zostać wyrzucony (i nie mów mi, żebym czytał dokumentację. Wiele razy wyjątek można propagować z głębi. dokumentacja nie jest aktualizowana lub poprawna). Czy jest jakieś narzędzie do sprawdzenia tego? (np. czytając kod Pythona i biblioteki)?


2
Pamiętaj, że w Pythonie <2.6 możesz również raiseużywać łańcuchów, a nie tylko BaseExceptionpodklas. Więc jeśli wywołujesz kod biblioteki, który jest poza twoją kontrolą, nawet except Exceptionnie jest wystarczający, ponieważ nie przechwytuje wyjątków ciągów. Jak zauważyli inni, szczekasz tutaj na niewłaściwe drzewo.
Daniel Pryden,

Nie wiedziałem tego. Pomyślałem, że poza Wyjątkiem: .. łapie prawie wszystko.
GabiMe

2
except Exceptiondziała dobrze do przechwytywania wyjątków ciągów w Pythonie 2.6 i nowszych.
Jeffrey Harris,

Odpowiedzi:


22

Myślę, że rozwiązanie może być nieprecyzyjne tylko z powodu braku statycznych reguł pisania.

Nie znam jakiegoś narzędzia, które sprawdza wyjątki, ale możesz wymyślić własne narzędzie pasujące do twoich potrzeb (dobra okazja, aby pobawić się trochę analizą statyczną).

Jako pierwszą próbę możesz napisać funkcję, która buduje AST, wyszukuje wszystkie Raisewęzły, a następnie próbuje znaleźć typowe wzorce zgłaszania wyjątków (np. Bezpośrednie wywołanie konstruktora)

Niech xbędzie następujący program:

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

Zbuduj AST za pomocą compilerpakietu:

tree = compiler.parse(x)

Następnie zdefiniuj Raiseklasę gości:

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

I przejdź przez Raisewęzły zbierające AST :

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),
]

Możesz kontynuować, rozwiązując symbole za pomocą tabel symboli kompilatora, analizując zależności danych itp. Możesz też po prostu wywnioskować, że CallFunc(Name('IOError'), ...)„zdecydowanie powinno oznaczać podniesienie IOError”, co jest całkiem OK dla szybkich praktycznych rezultatów :)


Dzięki za tę interesującą odpowiedź. Nie rozumiałem jednak, dlaczego mam szukać czegoś więcej niż wszystkie węzły podnoszenia. Dlaczego powinienem „rozwiązywać symbole przy użyciu tabel symboli kompilatora, analizować zależności danych”? Czy nie jedynym sposobem na zgłoszenie wyjątku jest metoda raise ()?
GabiMe

1
Biorąc pod uwagę v.nodespowyższą wartość, nie możesz właściwie powiedzieć, co to jest Name('IOError')lub Name('e'). Nie wiesz, jakie wartości te IOErrori emogą wskazywać, ponieważ są to tak zwane zmienne swobodne. Nawet jeśli ich kontekst wiązania byłby znany (tutaj wchodzą w grę tabele symboli), powinieneś przeprowadzić jakąś analizę zależności danych, aby wywnioskować ich dokładne wartości (powinno to być trudne w Pythonie).
Andrey Vlasovskikh

Ponieważ szukasz praktycznego rozwiązania półautomatycznego, lista ['IOError(errno.ENOENT, "not found")', 'e']wyświetlanych użytkownikowi jest w porządku. Ale nie możesz wywnioskować rzeczywistych klas wartości zmiennych reprezentowanych przez ciągi znaków :) (przepraszam za ponowne
wysłanie

1
Tak. Ta metoda, choć sprytna, w rzeczywistości nie zapewnia pełnego pokrycia. Ze względu na dynamiczną naturę Pythona jest całkowicie możliwe (choć oczywiście zły pomysł), aby kod, do którego dzwonisz, wykonał coś podobnego exc_class = raw_input(); exec "raise " + exc_class. Chodzi o to, że tego rodzaju statyczna analiza nie jest naprawdę możliwa w dynamicznym języku, takim jak Python.
Daniel Pryden,

7
Nawiasem mówiąc, możesz po prostu find /path/to/library -name '*.py' | grep 'raise 'uzyskać podobne wyniki :)
Andrey Vlasovskikh

23

Powinieneś łapać tylko wyjątki, które będziesz obsługiwać.

Łapanie wszystkich wyjątków według ich konkretnych typów jest nonsensem. Powinieneś wychwycić określone wyjątki, które możesz obsłużyć i które będziesz obsługiwać. W przypadku innych wyjątków możesz napisać ogólny catch, który przechwytuje „podstawowy wyjątek”, rejestruje go (użyj str()funkcji) i kończy działanie programu (lub robi coś innego, co jest odpowiednie w przypadku awarii).

Jeśli naprawdę zamierzasz obsłużyć wszystkie wyjątki i jesteś pewien, że żaden z nich nie jest fatalny (na przykład, jeśli uruchamiasz kod w jakimś środowisku piaskownicy), to twoje podejście do przechwytywania ogólnego BaseException pasuje do twoich celów.

Możesz być także zainteresowany odniesieniem do wyjątków językowych , a nie odniesieniem do biblioteki, której używasz.

Jeśli odniesienie do biblioteki jest naprawdę słabe i nie rzuca ponownie własnych wyjątków podczas przechwytywania wyjątków systemowych, jedynym użytecznym podejściem jest uruchomienie testów (może dodać je do zestawu testów, ponieważ jeśli coś jest nieudokumentowane, może się to zmienić!) . Usuń plik kluczowy dla twojego kodu i sprawdź, który wyjątek jest generowany. Podaj zbyt dużo danych i sprawdź, jaki błąd powoduje.

I tak będziesz musiał uruchomić testy, ponieważ nawet gdyby istniała metoda uzyskiwania wyjątków przez kod źródłowy, nie dałoby ci to żadnego pojęcia, jak powinieneś sobie z tym poradzić . Może powinien pojawić się komunikat o błędzie „Nie znaleziono pliku potrzebnego.txt!” kiedy złapiesz IndexError? Tylko test może powiedzieć.


27
Jasne, ale jak można zdecydować, z którymi wyjątkami powinien się obchodzić, jeśli nie wie, co może zostać wyrzucone?
GabiMe

@ bugspy.net, poprawiłem moją odpowiedź, aby odzwierciedlić tę sprawę
P Shved

Może czas na analizator kodu źródłowego, który może się tego dowiedzieć? Myślę, że nie powinno być zbyt trudno się rozwijać
GabiMe

@ bugspy.net, ośmieliłem klauzulę, dlaczego może nie być na to czas.
P Shved

Pewnie, że masz rację. Jednak nadal może być interesujące - w trakcie opracowywania - wiedzieć, jakie typy wyjątków mogą wystąpić.
hek2mgl

11

Odpowiednim narzędziem do rozwiązania tego problemu są unittesty. Jeśli masz wyjątki wywołane przez rzeczywisty kod, których unittesty nie powodują, potrzebujesz więcej unittestów.

Rozważ to

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

kaczką może być dowolny przedmiot

Oczywiście możesz mieć, AttributeErrorjeśli kaczka nie ma szarlatanów, a TypeErrorjeśli kaczka ma szarlatanę, ale nie można jej wywołać. Nie masz jednak pojęcia, co duck.quack()może wywołać, może nawet DuckErrorczy coś

Przypuśćmy, że masz taki kod

arr[i] = get_something_from_database()

Jeśli podniesie IndexError , nie wiesz, czy pochodzi z arr [i], czy też z wnętrza funkcji bazy danych. zwykle nie ma tak wielkiego znaczenia, gdzie wystąpił wyjątek, raczej to, że coś poszło nie tak, a to, co chciałeś, się nie wydarzyło.

Przydatną techniką jest złapanie i może przebicie wyjątku w ten sposób

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

Po co w ogóle to łapać, skoro zamierzasz ją „podbić”?
Tarnay Kálmán

Nie musisz tego przebijać, tak miał wskazywać komentarz.
John La Rooy,

2
Możesz także zapisać gdzieś wyjątek, a następnie podbić
John La Rooy,

3
Nie sądzę, aby pisanie testów jednostkowych było odpowiedzią. Pytanie brzmi: „w jaki sposób mogę dowiedzieć się, jakich wyjątków się spodziewać”, a pisanie testów jednostkowych nie pomoże ci się tego dowiedzieć. W rzeczywistości, aby napisać test jednostkowy, musisz już wiedzieć, jakich wyjątków się spodziewać, więc aby napisać poprawny test jednostkowy, musisz również odpowiedzieć na oryginalne pytanie.
Bruno Ranschaert

6

Nikt do tej pory nie wyjaśnił, dlaczego nie możesz mieć pełnej, w 100% poprawnej listy wyjątków, więc pomyślałem, że warto to skomentować. Jednym z powodów jest pierwszorzędna funkcja. Powiedzmy, że masz taką funkcję:

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

Teraz aplmożna zgłosić każdy wyjątek, który się fpojawi. Chociaż w podstawowej bibliotece nie ma wielu takich funkcji, ma to wpływ na wszystko, co używa rozumienia list z niestandardowymi filtrami, mapowaniem, redukcją itp.

Dokumentacja i analizatory źródeł są tutaj jedynymi „poważnymi” źródłami informacji. Pamiętaj tylko, czego nie mogą zrobić.


5

Natknąłem się na to podczas korzystania z gniazda, chciałem poznać wszystkie warunki błędów, na które napotkałem (więc zamiast próbować tworzyć błędy i dowiedzieć się, jakie gniazdo ma, chciałem tylko zwięzłą listę). Ostatecznie skończyłem grep'owanie „/usr/lib64/python2.4/test/test_socket.py” dla „podbicia”:

$ 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,

To dość zwięzła lista błędów. Teraz oczywiście działa to tylko w indywidualnych przypadkach i zależy od dokładności testów (a zazwyczaj są). W przeciwnym razie musisz prawie wyłapać wszystkie wyjątki, zarejestrować je, przeanalizować i dowiedzieć się, jak sobie z nimi poradzić (co w przypadku testów jednostkowych nie byłoby trudne).


4
To wzmacnia mój argument, że obsługa wyjątków w Pythonie jest bardzo problematyczna, jeśli musimy użyć grep lub analizatorów źródła, aby poradzić sobie z czymś tak podstawowym (co na przykład w Javie istniało od pierwszego dnia. Czasami gadatliwość jest dobra. Java jest gadatliwa ale przynajmniej nie ma przykrych niespodzianek)
GabiMe

@GabiMe, To nie jest tak, że ta umiejętność (lub ogólnie wpisywanie statyczne) jest srebrną kulą, która zapobiega wszelkim błędom. Java jest pełna niemiłych niespodzianek. Dlatego eclipse regularnie się zawiesza.
John La Rooy,

2

Są dwa sposoby, które uznałem za przydatne. W pierwszym uruchom kod w iPythonie, który wyświetli typ wyjątku.

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

W drugim przypadku zadowalamy się zbyt dużym wyłapywaniem i z czasem je poprawiamy. Uwzględnij trywyrażenie w swoim kodzie i złap except Exception as err. Wydrukuj wystarczające dane, aby wiedzieć, który wyjątek został zgłoszony. Ponieważ wyjątki są generowane, ulepsz swój kod, dodając bardziej precyzyjną exceptklauzulę. Kiedy poczujesz, że złapałeś wszystkie istotne wyjątki, usuń ten all inclusive. Mimo wszystko to dobra rzecz, ponieważ połyka błędy programowania.

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

1

normalnie musiałbyś wyłapać wyjątek tylko w kilku wierszach kodu. Nie chciałbyś umieścić całej swojej mainfunkcji wtry except klauzuli. dla każdych kilku wierszy powinieneś teraz (lub mieć możliwość łatwego sprawdzenia), jakiego rodzaju wyjątek może zostać zgłoszony.

dokumenty mają wyczerpującą listę wbudowanych wyjątków . nie próbuj wykluczać tych wyjątków, których się nie spodziewasz, mogą być obsługiwane / oczekiwane w kodzie wywołującym.

edycja : to, co może zostać wyrzucone, zależy oczywiście od tego, co robisz! dostęp do losowego elementu ciągu IndexError:, losowego elementu dyktatu:, KeyErroritp.

Po prostu spróbuj uruchomić te kilka wierszy w IDLE i wywołaj wyjątek. Ale unittest byłoby oczywiście lepszym rozwiązaniem.


1
To nie odpowiada na moje proste pytanie. Nie pytam o to, jak zaprojektować obsługę wyjątków ani kiedy lub jak wyłapać. Pytam, jak dowiedzieć się, co może zostać rzucone
GabiMe

1
@ bugspy.net: Nie można zrobić tego, o co prosisz, i jest to całkowicie poprawne obejście.
Daniel Pryden,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.