Ręczne zgłaszanie (zgłaszanie) wyjątku w Pythonie

2261

Jak mogę zgłosić wyjątek w Pythonie, aby później mógł zostać przechwycony przez exceptblok?

TIMEX
źródło

Odpowiedzi:

2934

Jak ręcznie zgłosić / zgłosić wyjątek w Pythonie?

Użyj najbardziej szczegółowego konstruktora wyjątków, który semantycznie pasuje do Twojego problemu .

Bądź konkretny w swojej wiadomości, np .:

raise ValueError('A very specific bad thing happened.')

Nie zgłaszaj ogólnych wyjątków

Unikaj generowania Exception. Aby go złapać, musisz złapać wszystkie inne, bardziej szczegółowe wyjątki, które go dzielą.

Problem 1: Ukrywanie błędów

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

Na przykład:

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

Problem 2: Nie złapie się

A bardziej szczegółowe połowy nie złapią ogólnego wyjątku:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

Najlepsze praktyki: raiseoświadczenie

Zamiast tego użyj najbardziej szczegółowego konstruktora wyjątków, który semantycznie pasuje do Twojego problemu .

raise ValueError('A very specific bad thing happened')

co również pozwala na przekazanie dowolnej liczby argumentów do konstruktora:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

Dostęp do tych argumentów ma argsatrybut Exceptionobiektu. Na przykład:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

odciski

('message', 'foo', 'bar', 'baz')    

W Pythonie 2.5 dodano rzeczywisty messageatrybut na BaseExceptionrzecz zachęcania użytkowników do podklas wyjątków i zaprzestania ich używania args, ale wprowadzenie messagei oryginalne wycofanie argumentów zostało wycofane .

Najlepsze praktyki: exceptklauzula

Wewnątrz klauzuli wyjątku możesz na przykład zarejestrować, że wystąpił określony typ błędu, a następnie ponownie go podnieść. Najlepszym sposobem na to przy zachowaniu śladu stosu jest użycie instrukcji bare raise. Na przykład:

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

Nie modyfikuj błędów ... ale jeśli nalegasz.

Możesz zachować stacktrace (i wartość błędu) sys.exc_info(), ale jest to o wiele bardziej podatne na błędy i ma problemy z kompatybilnością między Pythonem 2 i 3 , wolisz używać samego systemu raisedo przebijania.

Aby wyjaśnić - sys.exc_info()zwraca typ, wartość i traceback.

type, value, traceback = sys.exc_info()

To jest składnia w Pythonie 2 - zauważ, że to nie jest kompatybilne z Pythonem 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

Jeśli chcesz, możesz zmodyfikować to, co dzieje się z Twoim nowym przebiciem - np. Ustawiając nowe argsdla instancji:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

I zachowaliśmy cały ślad podczas modyfikowania argumentów. Zauważ, że nie jest to najlepsza praktyka i jest to niepoprawna składnia w Pythonie 3 (utrudniająca zachowanie kompatybilności).

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

W Pythonie 3 :

    raise error.with_traceback(sys.exc_info()[2])

Ponownie: unikaj ręcznej manipulacji śladami. Jest mniej wydajny i bardziej podatny na błędy. A jeśli używasz wątków i sys.exc_infomożesz nawet uzyskać niewłaściwe śledzenie (szczególnie jeśli używasz obsługi wyjątków dla przepływu sterowania - czego osobiście chciałbym uniknąć).

Python 3, Łączenie wyjątków

W Pythonie 3 można łączyć wyjątki, które zachowują identyfikatory:

    raise RuntimeError('specific message') from error

Być świadomym:

  • ten ma umożliwić zmianę typu błąd podniesiony, a
  • nie jest to zgodne z Python 2.

Przestarzałe metody:

Można je łatwo ukryć, a nawet dostać się do kodu produkcyjnego. Chcesz zgłosić wyjątek, a ich zrobienie spowoduje wyjątek, ale nie ten, który był przeznaczony!

Prawidłowy w Pythonie 2, ale nie w Pythonie 3, jest następujący:

raise ValueError, 'message' # Don't do this, it's deprecated!

Działa tylko w dużo starszych wersjach Pythona (2.4 i niższych), możesz nadal widzieć ludzi generujących ciągi znaków:

raise 'message' # really really wrong. don't do this.

We wszystkich nowoczesnych wersjach spowoduje to podniesienie a TypeError, ponieważ nie podbijasz BaseExceptiontypu. Jeśli nie sprawdzasz właściwego wyjątku i nie masz recenzenta, który jest świadomy problemu, może on zostać wprowadzony do produkcji.

Przykładowe użycie

Zgłaszam wyjątki, aby ostrzec konsumentów o moim interfejsie API, jeśli używają go niepoprawnie:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

Twórz własne typy błędów podczas apropos

„Chcę celowo popełnić błąd, aby przejść do wyjątku”

Możesz utworzyć własne typy błędów, jeśli chcesz wskazać, że coś jest nie tak z aplikacją, po prostu podklasuj odpowiedni punkt w hierarchii wyjątków:

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

i użycie:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')
Aaron Hall
źródło
19
Dzięki za to, właśnie tego potrzebowałem. Samo raiseto, czego potrzebowałem, aby móc wykonać niestandardowe debugowanie błędów na wielu poziomach wykonania kodu bez przerywania śledzenia stosu.
CaffeineConnoisseur
To świetna odpowiedź. Ale nadal pracuję z dużą ilością kodu 2.7 i często chcę dodać informacje do nieoczekiwanego wyjątku, takiego jak pozycja pliku wejściowego lub wartości niektórych zmiennych, ale zachowaj oryginalny stos i wyjątek. Mogę to zarejestrować, ale czasami nie chcę, aby było rejestrowane, np. Jeśli kod macierzysty ostatecznie go obsługuje. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]wydaje się robić to, co chcę i nigdy nie miałem z tym problemów. Ale wydaje się to zuchwałe i nie jest przyjętą praktyką. Czy jest lepszy sposób?
Michael Scheper
2
@brennanyoung W tym kontekście myślę, że może być mylące zgłaszanie błędu SyntaxError - prawdopodobnie powinieneś podnieść niestandardowy wyjątek. Wyjaśniam, jak tutaj: stackoverflow.com/a/26938914/541136
Aaron Hall
2
Zauważ, że pełny cytat brzmi: „Wszystkie wbudowane wyjątki niesystemowe wychodzą z tej klasy. Wszystkie wyjątki zdefiniowane przez użytkownika powinny również pochodzić z tej klasy”. - Oznacza to głównie, że nie powinieneś używać jednego z 4 wyjątków, które nie wynikają z Exceptiontwojej klasy nadrzędnej - możesz podklasować coś bardziej szczegółowego i powinieneś to zrobić, jeśli ma to sens.
Aaron Hall
1
W przykładzie „ Najlepsze praktyki: klauzula wyjątkiem ” używasz niezdefiniowanego AppErrorwyjątku. Może być lepiej użyć wbudowanego błędu, takiego jakAttributeError
Stevoisiak
530

NIE Rób tego . Podnoszenie gołe Exceptionjest absolutnie nie słuszne zrobić; zamiast tego zobacz doskonałą odpowiedź Aarona Halla .

Nie można uzyskać dużo bardziej pythonicznego niż to:

raise Exception("I know python!")

Zobacz dokumentację instrukcji wywoływania Pythona, jeśli chcesz uzyskać więcej informacji.

Gabriel Hurley
źródło
67
Nie prosze! Eliminuje to możliwość bycia konkretnym na temat tego, co łapiesz. Jest to CAŁKOWICIE zły sposób na zrobienie tego. Spójrz na doskonałą odpowiedź Aarona Halla zamiast tej. Czasy takie jak to, chciałbym móc wyrazić więcej niż jeden głos negatywny na odpowiedź.
Dawood ibn Kareem
27
@PeterR Równie straszne jest to, że ma tak mało głosów negatywnych. DLA KAŻDEGO czytającego tę odpowiedź, NIE RÓB TO NIGDY! Prawidłowa odpowiedź to Aaron Hall.
Dawood ibn Kareem
6
Myślę, że powinno być bardziej szczegółowe wyjaśnienie, dlaczego jest to złe lub tak złe.
Charlie Parker
9
@CharlieParker Jest. To pierwsza część odpowiedzi Aarona Halla .
Dinei
5
Dlaczego nie można oznaczyć tej odpowiedzi do usunięcia? Ma już 93 głosów negatywnych!
codeforester,
54

W Python3 istnieją 4 różne składnie wyjątków rasowania:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. zgłaszaj wyjątek vs. 2. zgłaszaj wyjątek (argumenty)

Jeśli użyjesz raise exception (args) do zgłoszenia wyjątku, argszostanie on wydrukowany podczas drukowania obiektu wyjątku - jak pokazano w poniższym przykładzie.

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3. podniesienie

raiseinstrukcja bez żadnych argumentów ponownie podnosi ostatni wyjątek. Jest to przydatne, jeśli po wykonaniu wyjątku trzeba wykonać pewne czynności, a następnie ponownie go podnieść. Ale jeśli wcześniej nie było wyjątku, raiseoświadczenie podnosi TypeErrorwyjątek.

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. zgłaszać wyjątek (argumenty) od oryginalnego_wyjątku

Ta instrukcja służy do tworzenia łańcucha wyjątków, w którym wyjątek zgłoszony w odpowiedzi na inny wyjątek może zawierać szczegóły oryginalnego wyjątku - jak pokazano w poniższym przykładzie.

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

Wynik:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero
N Randhawa
źródło
7
Należy pamiętać, że PEP8 woli exception(args)niżexception (args)
Gloweye
Można również raise exception(args) from Nonepowiedzieć, że obecnie aktywny wyjątek został obsłużony i nie jest już interesujący. W przeciwnym razie, jeśli zgłosisz wyjątek w exceptbloku i nie zostanie on obsłużony, identyfikatory śledzenia dla obu wyjątków zostaną pokazane oddzielone komunikatem „Podczas obsługi powyższego wyjątku wystąpił inny wyjątek”
cg909
35

W typowym przypadku, w którym musisz rzucić wyjątek w odpowiedzi na niektóre nieoczekiwane warunki i którego nigdy nie zamierzasz złapać, ale po prostu zawieść szybko, aby umożliwić debugowanie stamtąd, jeśli kiedykolwiek się zdarzy - najbardziej logiczny wydaje się być AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)
Jewgienij Siergiejew
źródło
19
Jest to lepszy przypadek, ValueErrorniż AssertionErrordlatego, że nie ma problemu z twierdzeniem (ponieważ nie ma tu miejsca) - problem dotyczy wartości. Jeśli naprawdę chcesz AssertionErrorw tym przypadku, napisz assert distance > 0, 'Distance must be positive'. Ale nie powinieneś błędu sprawdzać w ten sposób, ponieważ asercje można wyłączyć ( python -O).
Two-Bit Alchemist
1
@ Two-BitAlchemist Dobry punkt. Pomysł zagubił się w uproszczeniu, kiedy napisałem prosty przykład powyżej. W wielu podobnych przypadkach jest to stan, który nie jest powiązany z określoną wartością. Raczej znaczy „przepływ kontroli nigdy nie powinien się tutaj dostać”.
Evgeni Sergeevev,
2
@ Asercje Two-BitAlchemist można wyłączyć, tak, ale czy nie należy ich wcale używać do sprawdzania błędów?
Evgeni Sergeevev,
Cóż, to zależy. Nie pozwoliłbym, aby to był mój jedyny sprawdzanie błędów w programie, który zamierzałem rozpowszechniać. Z drugiej strony, mógłbym stworzyć program tylko dla moich współpracowników i powiedzieć im, że używają go na własne ryzyko, jeśli go uruchomią -O.
Two-Bit Alchemist
1
@ Two-BitAlchemist Dla mnie rolą asercji nie jest samo sprawdzanie błędów (do czego służy testowanie), ale ustawiają ogrodzenia w kodzie, przez które niektóre błędy nie mogą się przedostać. Łatwiej więc wyśledzić i odizolować błędy, które nieuchronnie się pojawią. To tylko dobre nawyki, które wymagają niewielkiego wysiłku, podczas gdy testowanie wymaga dużo wysiłku i czasu.
Evgeni Sergeev
12

Najpierw przeczytaj istniejące odpowiedzi, to tylko dodatek.

Zauważ, że możesz zgłaszać wyjątki z argumentami lub bez.

Przykład:

raise SystemExit

wychodzi z programu, ale możesz chcieć wiedzieć, co się stało, więc możesz tego użyć.

raise SystemExit("program exited")

spowoduje to wydrukowanie „programu zakończonego” na stderr przed zamknięciem programu.

Anant Prakash
źródło
2
Czy nie jest to sprzeczne z paradygmatem OOP? Zakładam, że pierwszy przypadek generuje odwołanie do klasy, a drugi instancję SystemExit. Czy nie raise SystemExit()byłby to lepszy wybór? Dlaczego pierwszy w ogóle działa?
napalony
2

Innym sposobem na zgłoszenie wyjątku jest assert. Możesz użyć aser, aby sprawdzić, czy warunek jest spełniony, jeśli nie, to wzrośnie AssertionError. Aby uzyskać więcej informacji, zobacz tutaj .

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))
Rehan Haider
źródło
2

Uwaga: zdarzają się chwile, kiedy chcesz obsłużyć ogólne wyjątki. Jeśli przetwarzasz kilka plików i rejestrujesz swoje błędy, możesz chcieć wychwycić każdy błąd, który wystąpi dla pliku, zaloguj go i kontynuuj przetwarzanie pozostałych plików. W takim przypadku a

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

zablokuj dobry sposób, aby to zrobić. Nadal będziesz chciał raisestosować wyjątki, abyś wiedział, co one oznaczają.

markemus
źródło
0

W tym celu powinieneś nauczyć się instrukcji podnoszenia Pythona. Powinien być przechowywany w bloku try. Przykład -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Abhijeet.py
źródło