Czy używanie try-except-else w Pythonie jest dobrą praktyką?

Od czasu do czasu w Pythonie widzę blok:

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something

Jaki jest powód, dla którego próba-z wyjątkiem-innego-istnieje?

Nie lubię tego rodzaju programowania, ponieważ używa WYJĄTKÓW do sterowania przepływem. Jeśli jednak jest on zawarty w języku, musi być ku temu dobry powód, prawda?

Rozumiem, że wyjątki nie są błędami, i że powinny być używane tylko w wyjątkowych warunkach (np. staram się zapisać plik do dysk i nie ma więcej miejsca, a może nie mam uprawnień), a nie do kontroli przepływu.

Zwykle obsługuję wyjątki jako:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something

Lub jeśli naprawdę nie chcę zwracać niczego, jeśli wydarzy się wyjątek, to:

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception
Author: Aaron Hall, 2013-04-22

9 answers

"Nie wiem, czy to z niewiedzy, ale nie podoba mi się to rodzaj programowania, jak to jest za pomocą WYJĄTKÓW do wykonywania kontroli przepływu."

W świecie Pythona używanie WYJĄTKÓW do kontroli przepływu jest powszechne i normalne.

Nawet Programiści Pythona używają WYJĄTKÓW do sterowania przepływem i ten styl jest mocno upieczony w języku (tzn. protokół iteratora używa Stopiteracja do zakończenia pętli sygnału).

Dodatkowo, styl try-except jest używany, aby zapobiec Warunkom wyścigu związanym z niektórymi konstrukcjami "look-before-you-leap". Na przykład testowanie os./ align = "left" / istnieje wyniki w informacji, które mogą być nieaktualne do czasu korzystania z nich. Podobnie, Kolejkapełne zwraca informacje, które mogą być nieświeże. Styl try-except-else stworzy bardziej niezawodny kod w takich przypadkach.

"rozumiem, że wyjątki nie są błędy, powinny tylko być stosowane w wyjątkowych warunkach "

W niektórych innych językach reguła ta odzwierciedla ich normy kulturowe, które znajdują odzwierciedlenie w ich bibliotekach. "Reguła" opiera się również częściowo na względach wydajności dla tych języków.

Norma kulturowa Pythona jest nieco inna. W wielu przypadkach musisz używać WYJĄTKÓW do sterowania przepływem. Ponadto, użycie WYJĄTKÓW w Pythonie nie spowalnia otaczającego kodu i kodu wywołującego, jak to ma miejsce w niektórych języki skompilowane (np. CPython implementuje już kod do sprawdzania WYJĄTKÓW na każdym kroku, niezależnie od tego, czy faktycznie używasz WYJĄTKÓW, czy nie).

Innymi słowy, twoje zrozumienie, że "wyjątki są dla wyjątków" jest regułą, która ma sens w niektórych innych językach, ale nie w Pythonie.

" Jednakże, jeśli jest on zawarty w samym języku, musi istnieć dobry powód, prawda?"

Oprócz pomocy w unikaniu race-warunki, wyjątki są również bardzo przydatne do wyciągania błędów obsługi pętli zewnętrznych. Jest to niezbędna optymalizacja w językach interpretowanych, które nie mają tendencji do automatycznego niezmienniczego ruchu kodu pętli .

Wyjątki mogą również znacznie uprościć kod w typowych sytuacjach, gdy możliwość obsługi problemu jest daleko od miejsca, w którym problem powstał. Na przykład, jest to powszechne, aby mieć kod wywołujący najwyższego poziomu interfejsu użytkownika dla logiki biznesowej, która z kolei nazywa się niskopoziomowe procedury. Sytuacje pojawiające się w procedurach niskiego poziomu (takich jak duplikaty rekordów dla unikalnych kluczy w dostępie do bazy danych) mogą być obsługiwane tylko w kodzie najwyższego poziomu (np. poproszenie użytkownika o nowy klucz, który nie jest sprzeczny z istniejącymi kluczami). Użycie wyjątków dla tego rodzaju przepływu sterowania pozwala procedurom średniego poziomu całkowicie zignorować problem i być ładnie oddzielonym od tego aspektu kontroli przepływu.

Jest ładny wpis na blogu o niezbędności wyjątki tutaj .

Zobacz też tę odpowiedź przepełnienia stosu: czy wyjątki są naprawdę wyjątkowymi błędami?

"Jaki jest powód, dla którego próba-z wyjątkiem-innego istnieje?"

Sama klauzula else jest interesująca. Działa, gdy nie ma wyjątku, ale przed finally-clause. To jest jego główny cel.

Bez klauzuli else, jedyną opcją uruchomienia dodatkowego kodu przed finalizacją byłaby niezdarna praktyka dodawania kodu do klauzuli próbnej. To jest niezdarne, bo grozi zgłaszanie WYJĄTKÓW w kodzie, który nie miał być chroniony przez try-block.

Przypadek użycia dodatkowego niezabezpieczonego kodu przed finalizacją nie pojawia się zbyt często. Nie spodziewaj się więc, że zobaczysz wiele przykładów w opublikowanym kodzie. Jest dość rzadki.

Innym przypadkiem użycia klauzuli else jest wykonywanie akcji, które muszą wystąpić, gdy nie występuje wyjątek i które nie występują, gdy wyjątki są obsługiwane. Na przykład:

   recip = float('Inf')
   try:
       recip = 1 / f(x)
   except ZeroDivisionError:
       logging.info('Infinite result')
   else:
       logging.info('Finite result')

Na koniec, najczęstszym zastosowaniem klauzuli else w bloku try jest upiększanie (wyrównywanie wyników wyjątkowych i nie-wyjątkowych na tym samym poziomie wcięć). To użycie jest zawsze opcjonalne i nie jest bezwzględnie konieczne.

 449
Author: Raymond Hettinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-31 01:10:48

Jaki jest powód, dla którego próba-z wyjątkiem-innego-istnieje?

Blok try pozwala obsłużyć oczekiwany błąd. Blok except powinien przechwytywać tylko wyjątki, z którymi jesteś przygotowany. Jeśli obsłużysz nieoczekiwany błąd, Twój kod może zrobić coś złego i ukryć błędy.

Klauzula else zostanie wykonana, jeśli nie było błędów, a nie wykonując tego kodu w bloku try, unikniesz wychwycenia nieoczekiwanego błędu. Ponownie, wyłapanie nieoczekiwanego błędu może Ukryj robaki.

Przykład

Na przykład:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something

Pakiet "try, except" ma dwie opcjonalne klauzule, else i finally. Więc to właściwie try-except-else-finally.

else będzie oceniać tylko wtedy, gdy nie ma wyjątku od bloku try. Pozwala nam uprościć bardziej skomplikowany kod poniżej:

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something

Więc jeśli porównamy else do alternatywy (która może powodować błędy) widzimy, że zmniejsza to linie kodu i możemy mieć bardziej czytelny, możliwy do utrzymania, i mniej błędnych kodów.

finally

finally wykonaÄ ‡ bez wzglÄ ™ du na wszystko, nawet jeĹ " li inna linia jest oceniana za pomocÄ ... instrukcji return.

W podziale na pseudo-kod

To może pomóc rozbić to, w najmniejszej możliwej formie, która demonstruje wszystkie funkcje, z komentarzami. Załóżmy, że jest to poprawne składniowo (ale nie można go uruchomić, jeśli nazwy nie są zdefiniowane) pseudo-kod jest w funkcji.

Na przykład:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle_SomeException(the_exception)
    # Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
    generic_handle(the_exception)
    # Handle any other exception that inherits from Exception
    # - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
    # Avoid bare `except:`
else: # there was no exception whatsoever
    return something()
    # if no exception, the "something()" gets evaluated,
    # but the return will not be executed due to the return in the
    # finally block below.
finally:
    # this block will execute no matter what, even if no exception,
    # after "something" is eval'd but before that value is returned
    # but even if there is an exception.
    # a return here will hijack the return functionality. e.g.:
    return True # hijacks the return in the else clause above

To prawda że możemy umieścić kod w bloku else w bloku try zamiast tego, gdzie by działał, gdyby nie było wyjątków, ale co jeśli sam kod wywoła wyjątek tego rodzaju, który wyłapujemy? Pozostawienie go w bloku try ukryłoby ten błąd.

Chcemy zminimalizować linie kodu w bloku try, aby uniknąć wychwytywania WYJĄTKÓW, których się nie spodziewaliśmy, zgodnie z zasadą, że jeśli nasz kod zawiedzie, chcemy, aby zawieść głośno. To jest najlepsze praktyka .

Rozumiem, że wyjątki nie są błędami

W Pythonie większość WYJĄTKÓW to błędy.

Możemy wyświetlić hierarchię WYJĄTKÓW za pomocą pydoc. Na przykład w Pythonie 2:

$ python -m pydoc exceptions

Lub Python 3:

$ python -m pydoc builtins

Da nam hierarchię. Widzimy, że większość rodzajów Exception to błędy, chociaż Python używa niektórych z nich do takich rzeczy, jak kończenie for pętli (StopIteration). To jest Python 3 ' s hierarchia:

BaseException
    Exception
        ArithmeticError
            FloatingPointError
            OverflowError
            ZeroDivisionError
        AssertionError
        AttributeError
        BufferError
        EOFError
        ImportError
            ModuleNotFoundError
        LookupError
            IndexError
            KeyError
        MemoryError
        NameError
            UnboundLocalError
        OSError
            BlockingIOError
            ChildProcessError
            ConnectionError
                BrokenPipeError
                ConnectionAbortedError
                ConnectionRefusedError
                ConnectionResetError
            FileExistsError
            FileNotFoundError
            InterruptedError
            IsADirectoryError
            NotADirectoryError
            PermissionError
            ProcessLookupError
            TimeoutError
        ReferenceError
        RuntimeError
            NotImplementedError
            RecursionError
        StopAsyncIteration
        StopIteration
        SyntaxError
            IndentationError
                TabError
        SystemError
        TypeError
        ValueError
            UnicodeError
                UnicodeDecodeError
                UnicodeEncodeError
                UnicodeTranslateError
        Warning
            BytesWarning
            DeprecationWarning
            FutureWarning
            ImportWarning
            PendingDeprecationWarning
            ResourceWarning
            RuntimeWarning
            SyntaxWarning
            UnicodeWarning
            UserWarning
    GeneratorExit
    KeyboardInterrupt
    SystemExit

Komentator zapytał:

Powiedzmy, że masz metodę, która pingi zewnętrzne API i chcesz obsłużyć wyjątek w klasie poza opakowaniem API, czy po prostu zwracasz e Z metody pod klauzulą Exception, gdzie E jest obiektem wyjątku?

Nie, Nie zwracasz wyjątku, po prostu przekaż go gołym raise, aby zachować stacktrace.

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise

Lub, w Pythonie 3, możesz podnieść nowy wyjątek i zachować backtrace z wyjątkiem chaining:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception

I rozwinąć w moja odpowiedź tutaj .

 112
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-31 01:14:06

Python nie zgadza się z ideą, że wyjątki powinny być używane tylko w wyjątkowych przypadkach, w rzeczywistości idiom to 'proś o przebaczenie, a nie pozwolenie'. Oznacza to, że używanie WYJĄTKÓW jako rutynowej części kontroli przepływu jest całkowicie dopuszczalne i w rzeczywistości zachęcane.

Jest to ogólnie dobra rzecz, ponieważ praca w ten sposób pomaga uniknąć pewnych problemów( jako oczywisty przykład, często unika się warunków wyścigowych), a to sprawia, że kod jest nieco bardziej czytelne.

Wyobraź sobie, że masz sytuację, w której pobierasz dane użytkownika, które muszą być przetworzone, ale masz domyślną wartość, która jest już przetworzona. Struktura try: ... except: ... else: ... sprawia, że kod jest bardzo czytelny:

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)

Porównaj z tym, jak może działać w innych językach:

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value

Zwróć uwagę na zalety. Nie ma potrzeby sprawdzania poprawności wartości i analizowania jej oddzielnie, są one wykonywane raz. Kod również postępuje bardziej logicznie, główną ścieżką kodu jest pierwsza, po której następuje " if to nie działa, zrób to.

Przykład jest oczywiście trochę wymyślony, ale pokazuje, że istnieją przypadki dla tej struktury.

 28
Author: Gareth Latty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-22 01:59:14

Czy używanie try-except-else w Pythonie jest dobrą praktyką?

Odpowiedź jest taka, że jest zależna od kontekstu. Jeśli to zrobisz:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

To pokazuje, że nie znasz Pythona zbyt dobrze. Ta funkcjonalność jest zamknięta w metodzie dict.get:

item = d.get('item', 'default')

The try/except block to znacznie bardziej zaśmiecony wizualnie i wyrazisty sposób pisania tego, co można efektywnie wykonać w jednej linii za pomocą metody atomowej. Istnieją inne przypadki, w których to prawda.

Nie oznacza to jednak, że powinniśmy unikać obsługi wyjątków. W niektórych przypadkach preferowane jest unikanie warunków wyścigowych. Nie sprawdzaj, czy plik istnieje, po prostu spróbuj go otworzyć i złap odpowiedni IOError. W trosce o prostotę i czytelność spróbuj to ująć lub potraktować jako apropos.

[[6]}przeczytaj Zen Pythona, rozumiejąc, że istnieją zasady, które są w napięciu, i uważaj na dogmat, który opiera się zbyt mocno na każde z zawartych w nim stwierdzeń.
 14
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-03 16:54:25

Należy uważać na użycie bloku finally, ponieważ nie jest to to samo, co użycie bloku else w próbie, except. Blok finally zostanie uruchomiony niezależnie od wyniku próby except.

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

Jak wszyscy zauważyli użycie bloku else powoduje, że Twój kod jest bardziej czytelny i działa tylko wtedy, gdy wyjątek nie zostanie wyrzucony

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:
 4
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-24 00:53:17

Kiedy widzisz to:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y

Albo nawet to:

try:
    return 1 / x
except ZeroDivisionError:
    return None

Rozważ to zamiast:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x
 4
Author: Rajiv Bakulesh Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-13 23:40:49

To jest mój prosty fragment howto understanding try-except-else-finally block w Pythonie:

def div(a, b):
    try:
        a/b
    except ZeroDivisionError:
        print("Zero Division Error detected")
    else:
        print("No Zero Division Error")
    finally:
        print("Finally the division of %d/%d is done" % (a, b))

Spróbujmy div 1/1:

div(1, 1)
No Zero Division Error
Finally the division of 1/1 is done

Spróbujmy div 1/0

div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done
 1
Author: zakiakhmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-06 06:24:45

Zobacz poniższy przykład, który ilustruje wszystko o try-except-else-finally:

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

Zaimplementuj go i przyjdź:

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.
 0
Author: JawSaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-25 05:04:02

OP, MASZ RACJĘ. else po try / except w Pythonie jest brzydki . prowadzi do innego obiektu kontrolującego przepływ, w którym nie ma potrzeby:

try:
    x = blah()
except:
    print "failed at blah()"
else:
    print "just succeeded with blah"

Całkowicie jednoznacznym odpowiednikiem jest:

try:
    x = blah()
    print "just succeeded with blah"
except:
    print "failed at blah()"

Jest to znacznie jaśniejsze niż klauzula else. Else po try / except nie jest często pisane, więc zajmuje chwilę, aby dowiedzieć się, jakie są implikacje.

To, że możesz coś zrobić, nie znaczy, że powinieneś coś zrobić.

Wiele funkcji zostało dodano do języków, bo ktoś pomyślał, że może się przydać. Problem w tym, że im więcej funkcji, tym mniej jasne i oczywiste są rzeczy, ponieważ ludzie zwykle nie używają tych dzwonków i gwizdków.

Tylko moje 5 centów. Muszę wyjść z tyłu i posprzątać dużo kodu napisanego przez programistów pierwszego roku studiów, którzy myślą, że są mądrzy i chcą pisać kod w jakiś Uber-tight, Uber-efficient sposób, kiedy to po prostu sprawia, że bałagan, aby spróbować przeczytać / zmodyfikować później. Głosuję za czytelność codziennie i dwa razy w niedziele.
 -3
Author: Kevin J. Rice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-20 16:03:11