Pytanie Ręczne podnoszenie (wyrzucanie) wyjątku w Pythonie


W jaki sposób mogę podnieść wyjątek w Pythonie, aby później mógł zostać przechwycony przez except blok?


1524
2018-01-12 21:07


pochodzenie




Odpowiedzi:


Jak ręcznie rzucić / podnieść wyjątek w Pythonie?

Użyj najbardziej konkretnego 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 podnoszenia ogólnego wyjątku. Aby go złapać, będziesz musiał złapać wszystkie inne, bardziej specyficzne wyjątki, które je podklasują.

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

a bardziej specyficzne połowy nie wychwycą 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: raise komunikat

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

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

co również umożliwia ręczne przekazanie do konstruktora dowolnej liczby argumentów:

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

Te argumenty są dostępne przez args atrybut w obiekcie wyjątku. Na przykład:

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

drukuje

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

W Pythonie 2.5, rzeczywiste message atrybut został dodany do BaseException na rzecz zachęcania użytkowników do podklasy Wyjątki i przestać używać args, ale wprowadzenie message i oryginalne wycofanie argumentów zostało wycofane.

Najlepsze praktyki: except klauzula

Gdy znajdujesz się w klauzuli except, możesz np. Zarejestrować, że wystąpił określony typ błędu, a następnie ponownie go podnieść. Najlepszym sposobem, aby to zrobić, zachowując ślad stosu, jest użycie wyciągu "na gołe podwyższenie". 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 swoich błędów ... ale jeśli nalegasz.

Możesz zachować stacktrace (i wartość błędu) przy pomocy sys.exc_info(), ale to jest bardziej podatne na błędy i ma problemy ze zgodnością między Python 2 i 3, wolą używać gołego raise ponownie przebić.

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 nie jest to zgodne 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 nowym podbiciem - np. ustawianie nowych argumentów dla 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 traceback podczas modyfikowania argumentów. Zauważ, że tak jest nie najlepsza praktyka i to jest niepoprawna składnia w Pythonie 3 (dzięki czemu zachowanie zgodności jest znacznie trudniejsze do obejścia).

>>> 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 Python 3:

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

Ponownie: unikaj ręcznego manipulowania śladami. Jego mniej wydajny i bardziej podatny na błędy. A jeśli używasz wątkowania i sys.exc_info może nawet pojawić się niewłaściwe śledzenie (szczególnie jeśli używasz obsługi wyjątków do kontroli przepływu - której osobiście raczej unikam).

Python 3, Łańcuch wyjątków

W Pythonie 3 możesz łączyć Wyjątki, które zachowują informacje zwrotne:

    raise RuntimeError('specific message') from error

Być świadomym:

  • to robi pozwalają zmienić typ błędu podniesiony, i
  • to jest nie kompatybilny z Python 2.

Przestarzałe metody:

Mogą one łatwo ukryć się, a nawet dostać do kodu produkcyjnego. Chcesz zgłosić wyjątek, a zrobienie tego spowoduje zgłoszenie wyjątku, ale nie ten zamierzony!

Obowiązuje w Pythonie 2, ale nie w Pythonie 3 jest następujące:

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

Tylko ważne w starszych wersjach Pythona (2,4 i niższe), nadal możesz zobaczyć osoby, które podnoszą ciągi:

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

We wszystkich nowoczesnych wersjach, to faktycznie podniesie TypeError, ponieważ nie podnosisz typu BaseException. Jeśli nie sprawdzasz właściwego wyjątku i nie masz recenzenta, który jest świadomy problemu, może zostać uruchomiony.

Przykładowe użycie

Podnoszę wyjątki, aby ostrzec konsumentów mojego 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)))

Stwórz własne typy błędów, gdy są używane

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

Możesz tworzyć własne typy błędów, jeśli chcesz wskazać, że coś jest nie tak z twoją 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.')

1898
2018-06-05 16:30



Dzięki za to, dokładnie to, czego potrzebowałem. Gołe raise to było to, czego potrzebowałem, aby móc przeprowadzać własne debugowanie błędów na wielu poziomach wykonywania kodu bez przerywania śledzenia stosu. - CaffeineConnoisseur
To jest świetna odpowiedź. Ale nadal pracuję z dużą ilością kodu 2.7 i często znajduję się, że chcę dodać informacje do nieoczekiwanego wyjątku, takiego jak pozycja pliku wejściowego lub wartości niektórych zmiennych, ale zachowam oryginalny stos i wyjątek. Mogę to zarejestrować, ale czasami nie chcę, aby był on rejestrowany, np. jeśli kod nadrzędny 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 czuje się zjadliwy, a nie akceptowaną praktyką. Czy istnieje lepszy sposób? - Michael Scheper
Dzięki za komplement - ale niestety, nie ma lepszego sposobu - nie na Pythonie 2. - Aaron Hall♦
@brennanyoung W tym kontekście wydaje mi się, że może być mylące wywołać błąd składni - prawdopodobnie powinieneś podnieść niestandardowy wyjątek. Wyjaśniam, jak tutaj: stackoverflow.com/a/26938914/541136 - Aaron Hall♦
Zauważ, że pełna wycena to "Wszystkie wbudowane, nie kończące się systemem wyjątki pochodzą z tej klasy. Wszystkie zdefiniowane przez użytkownika wyjątki powinny być również wyprowadzone z tej klasy." - Oznacza to głównie, że nie powinieneś używać żadnego z 4 wyjątków, z których nie wynikają Exception jako klasa nadrzędna - możesz podklasować coś bardziej konkretnego i powinno to zrobić, jeśli ma to sens. - Aaron Hall♦


NIE ZROBIĆ TO. Podnoszenie gołego Exception jest absolutnie nie właściwa rzecz do zrobienia; widzieć Doskonała odpowiedź Aarona Halla zamiast.

Nie można uzyskać znacznie bardziej pytonowo niż to:

raise Exception("I know python!")

Widzieć oświadczenie o podniesieniu docs dla Pythona, jeśli potrzebujesz więcej informacji.


540
2018-01-12 21:08



Nie, proszę! To eliminuje możliwość określania, co złapiesz. Jest to CAŁKOWITA zła metoda. Rzuć okiem na doskonałą odpowiedź Aarona Halla zamiast tego. Czasami tak jest, chciałbym dać więcej niż jeden test na odpowiedź. - Dawood ibn Kareem
@DavidWallace to straszne, że ma tak wiele przebojów :( - Peter R
@PeterR To równie straszne, że ma tak mało pochlebstw. DLA KAŻDEGO czytającego tę odpowiedź NIE ZRÓB TO NIGDY! Poprawną odpowiedzią jest Aaron Hall. - Dawood ibn Kareem
Myślę, że powinno być bardziej szczegółowe wyjaśnienie, dlaczego jest to złe lub złe. - Charlie Parker
@CharlieParker Jest. To pierwsza część Odpowiedź Aarona Hali. - Dinei


Typowy przypadek, w którym trzeba rzucić wyjątek w odpowiedzi na pewne nieoczekiwane warunki, i że nigdy nie zamierza się go przechwycić, ale po prostu szybko się zawieść, aby umożliwić ci debugowanie, jeśli kiedykolwiek się zdarzy - najbardziej logiczne wydaje się być AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)

29
2018-05-19 04:55



Jest to lepsze rozwiązanie ValueError niż AssertionError ponieważ nie ma problemu z asercją (ponieważ tutaj nic nie jest robione) - problem dotyczy wartości. Jeśli naprawdę chcesz AssertionError w takim przypadku napisz assert distance > 0, 'Distance must be positive'. Ale nie powinieneś tego sprawdzać, ponieważ asercje mogą być wyłączone (python -O). - Two-Bit Alchemist
@ Dwukanałowy Alchemik Dobry punkt. Pomysł zaginął w uproszczeniu, kiedy napisałem prosty przykład powyżej. W wielu podobnych przypadkach jest to stan, który nie jest związany z określoną wartością. Przeciwnie, znaczenie ma "kontrola przepływu nigdy nie powinna tu dotrzeć". - Evgeni Sergeev
@ Asercje dwu-bitowo-alchemiczne można wyłączyć, tak, ale czy nie powinieneś ich używać do sprawdzania błędów? - Evgeni Sergeev
Cóż, to zależy. Nie pozwoliłbym, żeby to było moje jedyne sprawdzanie błędów w programie, który zamierzałem dystrybuować. 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 używają go z -O. - Two-Bit Alchemist
@ Two-BitAlchemist Dla mnie rola asercji nie polega na sprawdzaniu błędów jako takich (do czego służy testowanie), ale ustawiają w kodzie pewne ogrodzenia, których niektóre błędy nie mogą ominąć. Łatwiej jest więc wyśledzić i wyizolować błędy, które nieuchronnie wystąpią. To tylko dobre nawyki, które wymagają niewiele wysiłku, podczas gdy testowanie wymaga dużego nakładu pracy i dużo czasu. - Evgeni Sergeev


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

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

1. podniesienie wyjątku vs. 2. podniesienie wyjątku (args)

Jeśli użyjesz raise exception (args)  podnieść wyjątek, a następnie args zostanie wydrukowany po wydrukowaniu obiektu wyjątku - jak pokazano w przykładzie poniżej.

  #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.raise

raise oświadczenie bez żadnych argumentów ponownie podnosi ostatni wyjątek. Jest to przydatne, jeśli musisz wykonać pewne czynności po wychwyceniu wyjątku, a następnie chcesz go ponownie podnieść. Ale jeśli wcześniej nie było wyjątku, raise oświadczenie podnosi TypeError Wyją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. Podnieść wyjątek (args) z original_exception

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 przykładzie poniżej.

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__)

Wydajność:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero

23
2017-11-08 17:54





Przeczytaj najpierw istniejące odpowiedzi, to tylko dodatek.

Zauważ, że możesz podnosić wyjątki z argumentami lub bez nich.

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 wypisanie "programu zakończonego" do stderr przed zamknięciem programu.


5
2018-03-29 11:59