Python try-else


578

Jakie jest zamierzone zastosowanie opcjonalnej elseklauzuli tryoświadczenia?


1
Większość odpowiedzi wydaje się koncentrować na tym, dlaczego nie możemy po prostu umieścić materiału w klauzuli else w samej klauzuli try. Pytanie stackoverflow.com/questions/3996329 pyta konkretnie, dlaczego kod klauzuli else nie może przejść po samym bloku try, i pytanie to jest duplikowane na to pytanie, ale nie widzę tutaj wyraźnej odpowiedzi na to pytanie. Uważam, że stackoverflow.com/a/3996378/1503120 doskonale odpowiada na to pytanie. Próbowałem również wyjaśnić różne znaczenie różnych klauzul na stackoverflow.com/a/22579805/1503120 .
jamadagni

Chcesz, aby coś się wydarzyło, jeśli wyjątek nie zostanie wyzwolony, przed końcowym czyszczeniem, który nigdy nie powinien sam wywołać tej samej obsługi wyjątków.
benjimin

Odpowiedzi:


857

Instrukcje w elsebloku są wykonywane, jeśli wykonanie nie powiedzie się try- jeśli nie było wyjątku. Szczerze mówiąc, nigdy nie znalazłem potrzeby.

Jednak uwagi na temat obsługi wyjątków :

Użycie klauzuli else jest lepsze niż dodanie dodatkowego kodu do klauzuli try, ponieważ pozwala uniknąć przypadkowego wyłapania wyjątku, który nie został zgłoszony przez kod chroniony przez instrukcję try ....

Tak więc, jeśli masz metodę, która może na przykład rzucić an IOError, i chcesz wychwycić wyjątki, które ona wywołuje, ale jest coś innego, co chcesz zrobić, jeśli pierwsza operacja się powiedzie, i nie chcesz wychwycić błędu IOError z podczas tej operacji możesz napisać coś takiego:

try:
    operation_that_can_throw_ioerror()
except IOError:
    handle_the_exception_somehow()
else:
    # we don't want to catch the IOError if it's raised
    another_operation_that_can_throw_ioerror()
finally:
    something_we_always_need_to_do()

Jeśli po prostu wstawisz another_operation_that_can_throw_ioerror()później operation_that_can_throw_ioerror, exceptzłapie błędy drugiego połączenia. A jeśli umieścisz go po całym trybloku, zawsze będzie uruchamiany, i dopiero po finally. elsePozwala upewnić

  1. druga operacja jest uruchamiana tylko wtedy, gdy nie ma wyjątku,
  2. działa przed finallyblokiem i
  3. wszelkie IOErrorpodniesione przez niego przedmioty nie są tutaj łapane

7
Należy również pamiętać, że zmienne użyte w bloku try mogą być użyte w bloku else, dlatego należy zawsze rozważyć użycie tego wariantu, jeśli nie spodziewa się się więcej wyjątków w bloku else
WorldSEnder

3
To nie ma znaczenia, ponieważ zmienne o zasięgu próbnym są widoczne poza próbą, niezależnie od tego, czy jest coś innego.
Reinderien

36
Nie ma czegoś takiego jak „zmienna o zasięgu próbnym”. W Pythonie zakresy zmiennych są ustalane tylko przez moduły, funkcje i pojęcia, a nie struktury kontrolne.
msmsm

9
Klauzula else pozwala pisać kod, który ma sens tylko wtedy, gdy wyjątek nie zostanie zgłoszony; klauzula wyjątku może po prostu przejść. Jeśli umieścisz logikę w bloku try, ryzykujesz ukrywanie błędów w kodzie. Nigdy nie zgniataj wyjątków, których się nie spodziewałeś.
Alice Purcell,

9
z tej odpowiedzi nie wynika jasno, co oznacza „upadek z dna” - dzieje się tak nie tylko z powodu wyjątku, ale również z powodu return, continuelub break.
Antti Haapala

108

Jest jeden duży powód do użycia else- styl i czytelność. Zasadniczo dobrym pomysłem jest przechowywanie kodu, który może powodować wyjątki w pobliżu kodu, który się nimi zajmuje. Na przykład porównaj te:

try:
    from EasyDialogs import AskPassword
    # 20 other lines
    getpass = AskPassword
except ImportError:
    getpass = default_getpass

i

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
else:
    # 20 other lines
    getpass = AskPassword

Drugi jest dobry, gdy except nie można wrócić wcześniej lub ponownie rzucić wyjątek. Jeśli to możliwe, napisałbym:

try:
    from EasyDialogs import AskPassword
except ImportError:
    getpass = default_getpass
    return False  # or throw Exception('something more descriptive')

# 20 other lines
getpass = AskPassword

Uwaga: Odpowiedź skopiowana z ostatnio opublikowanego duplikatu tutaj , stąd wszystkie te rzeczy „AskPassword”.


53

Jedno zastosowanie: przetestuj kod, który powinien zgłosić wyjątek.

try:
    this_should_raise_TypeError()
except TypeError:
    pass
except:
    assert False, "Raised the wrong exception type"
else:
    assert False, "Didn't raise any exception"

(Ten kod należy streścić w bardziej ogólny test w praktyce).


50

Python try-else

Jakie jest zamierzone użycie opcjonalnej elseklauzuli instrukcji try?

Zamierzonym zastosowaniem jest kontekst, w którym można uruchomić więcej kodu, jeśli nie ma wyjątków, w których oczekiwano, że będzie on obsługiwany.

Ten kontekst pozwala uniknąć przypadkowej obsługi błędów, których się nie spodziewałeś.

Ale ważne jest, aby zrozumieć, o dokładnych warunkach, które powodują jeszcze klauzuli do biegu, ponieważ return, continuei breakmoże przerwać przepływ sterowania do else.

W podsumowaniu

elseOświadczenie biegnie jeśli istnieją żadne wyjątki, a jeśli nie przerywa return, continuelub breakoświadczenia.

W innych odpowiedziach brakuje tej ostatniej części.

Z dokumentów:

Opcjonalna elseklauzula jest wykonywana wtedy, gdy kontrola spływa do końca tego trypunktu. *

(Dodano pogrubienie.) A przypis brzmi:

* Obecnie kontrola „spływa końca” z wyjątkiem przypadku wyjątku lub wykonania return, continuelub breakoświadczenia.

Wymaga co najmniej jednego poprzedzającego zdania z wyjątkiem klauzuli ( patrz gramatyka ). Więc tak naprawdę to nie jest „try-else”, to „try-else-else (-finally)” z else(ifinally ) są opcjonalne.

W Python Tutorial omawia jego wykorzystania:

Instrukcja try ... else ma opcjonalną klauzulę else, która, jeśli jest obecna, musi być zgodna ze wszystkimi klauzulami oprócz. Jest to przydatne w przypadku kodu, który należy wykonać, jeśli klauzula try nie zgłosi wyjątku. Na przykład:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

Użycie klauzuli else jest lepsze niż dodanie dodatkowego kodu do klauzuli try, ponieważ pozwala uniknąć przypadkowego wyłapania wyjątku, który nie został zgłoszony przez kod chroniony przez instrukcję try ....

Przykład różnicowania elsekodu w zależności odtry bloku

Jeśli obsłużysz błąd, elseblok nie będzie działał. Na przykład:

def handle_error():
    try:
        raise RuntimeError('oops!')
    except RuntimeError as error:
        print('handled a RuntimeError, no big deal.')
    else:
        print('if this prints, we had no error!') # won't print!
    print('And now we have left the try block!')  # will print!

I teraz,

>>> handle_error()
handled a RuntimeError, no big deal.
And now we have left the try block!

26

Try-else-else jest świetny do łączenia wzoru EAFP z pisaniem kaczek :

try:
  cs = x.cleanupSet
except AttributeError:
  pass
else:
  for v in cs:
    v.cleanup()

Być może ten naiwny kod jest w porządku:

try:
  for v in x.cleanupSet:
    v.clenaup()
except AttributeError:
  pass

To świetny sposób na przypadkowe ukrycie poważnych błędów w kodzie. Napisałem tam porządek, ale błąd AttributeError, który dałby mi znać, został połknięty. Co gorsza, co jeśli napisałbym go poprawnie, ale metoda czyszczenia była czasami przekazywana typowi użytkownika, który miał błędnie nazwany atrybut, powodując, że po cichu zawodzi w połowie i pozostawia plik niezamknięty? Powodzenia w debugowaniu tego.


19

Uważam, że jest to bardzo przydatne, gdy masz porządek, aby to zrobić, nawet jeśli istnieje wyjątek:

try:
    data = something_that_can_go_wrong()
except Exception as e: # yes, I know that's a bad way to do it...
    handle_exception(e)
else:
    do_stuff(data)
finally:
    clean_up()

9

Chociaż nie możesz teraz wymyślić zastosowania, możesz się założyć, że musi to być użyteczne. Oto niewyobrażalna próbka:

Z else:

a = [1,2,3]
try:
    something = a[2]
except:
    print "out of bounds"
else:
    print something

Bez else:

try:
    something = a[2]
except:
    print "out of bounds"

if "something" in locals():
    print something

Tutaj masz somethingzdefiniowaną zmienną, jeśli nie zostanie zgłoszony błąd. Możesz usunąć to poza tryblokiem, ale wymaga to pewnego niechlujnego wykrycia, jeśli zmienna jest zdefiniowana.


3
Co jest złego w something = a[2]; print somethingśrodku try: block?
S.Lott

@ S. Nie ma nic, ale co, jeśli ktoś wysyła Ci listę, a Ty nie chcesz wyświetlać danych, jeśli nie są wystarczająco długie, ponieważ prawdopodobnie są uszkodzone?
Nieznany

12
S. Lott: „wydrukuj coś” może wywołać inny wyjątek, którego nie chcesz przechwycić.
Darius Bacon

Nie widzę różnicy. Jeśli otrzymam wyjątek poza zakresem, wypisze „poza zakresem”. Zrozumiałeś. Jeśli dostanę jakiś inny wyjątek, ten blok kodu nie przechwyci go. Jeśli nie otrzymam wyjątku, zachowanie polega na wypisaniu wartości czegoś, co jest [2]. Nie widzę, co robią inne w tym przykładzie.
S.Lott

3
Wartość „coś”, po wydrukowaniu, może wywołać błąd w metodzie __str __ (). Chociaż w tym przykładzie ta wartość wynosi w rzeczywistości 2, równie dobrze możesz wskazać, że nie ma tu również wyjątku poza zakresem.
Darius Bacon

8

Jest ładny przykład try-elsew PEP 380 . Zasadniczo sprowadza się to do obsługi różnych wyjątków w różnych częściach algorytmu.

To jest coś takiego:

try:
    do_init_stuff()
except:
    handle_init_suff_execption()
else:
    try:
        do_middle_stuff()
    except:
        handle_middle_stuff_exception()

Umożliwia to zapisanie kodu obsługi wyjątku bliżej miejsca wystąpienia wyjątku.


7

Od błędów i wyjątków # Obsługa wyjątków - docs.python.org

try ... exceptOświadczenie ma opcjonalną elseklauzulę, która, jeśli jest obecny, musi przestrzegać wszystkich z wyjątkiem klauzul. Jest to przydatne w przypadku kodu, który należy wykonać, jeśli klauzula try nie zgłosi wyjątku. Na przykład:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'cannot open', arg
    else:
        print arg, 'has', len(f.readlines()), 'lines'
        f.close()

Użycie klauzuli else jest lepsze niż dodanie dodatkowego kodu do klauzuli try, ponieważ pozwala uniknąć przypadkowego wyłapania wyjątku, który nie został zgłoszony przez kod chroniony przez instrukcję try ....


6

Patrząc na odniesienie do Pythona , wydaje się, że elsejest wykonywane po, trygdy nie ma wyjątku. Opcjonalna klauzula else jest wykonywana, gdy kontrola wypływa z końca klauzuli try. 2 Wyjątki w klauzuli else nie są obsługiwane przez poprzednie klauzule oprócz.

Zanurz się w pythonie ma przykład, w którym, jeśli dobrze rozumiem, w trybloku próbują zaimportować moduł, gdy to się nie powiedzie, otrzymasz wyjątek i powiąż domyślnie, ale gdy to działa, masz opcję, aby przejść doelse bloku i powiązania tego, co jest wymagane (zobacz link do przykładu i objaśnienia).

Jeśli spróbujesz wykonać pracę w catchbloku, może to rzucić kolejny wyjątek - myślę, że właśnie tam elseprzydatny jest blok.


4
„Wyjątki od klauzuli else nie są obsługiwane przez poprzednie klauzule oprócz.” To jest przydatna część. Dziękuję Ci.
geowa4

„Opcjonalna klauzula else jest wykonywana, gdy kontrola wypływa z końca klauzuli try”, to kolejna różnica, ponieważ można powrócić z trybloku.
Tomer W

4

Otóż ​​to. Blok „else” klauzuli try-wyjątkiem istnieje dla kodu, który jest uruchamiany, gdy (i tylko wtedy), gdy próba się powiedzie. Można go używać i nadużywać.

try:
    fp= open("configuration_file", "rb")
except EnvironmentError:
    confdata= '' # it's ok if the file can't be opened
else:
    confdata= fp.read()
    fp.close()

# your code continues here
# working with (possibly empty) confdata

Osobiście podoba mi się i używam go w razie potrzeby. Semantycznie grupuje instrukcje.


2

Być może zastosowanie może być:

#debug = []

def debuglog(text, obj=None):
    " Simple little logger. "
    try:
        debug   # does global exist?
    except NameError:
        pass    # if not, don't even bother displaying
    except:
        print('Unknown cause. Debug debuglog().')
    else:
        # debug does exist.
        # Now test if you want to log this debug message
        # from caller "obj"
        try:
            if obj in debug:
                print(text)     # stdout
        except TypeError:
            print('The global "debug" flag should be an iterable.')
        except:
            print('Unknown cause. Debug debuglog().')

def myfunc():
    debuglog('Made it to myfunc()', myfunc)

debug = [myfunc,]
myfunc()

Może to też przyniesie ci korzyść.


2

Znalazłem try: ... else:konstrukcję przydatną w sytuacji, gdy uruchamiasz zapytania do bazy danych i logujesz wyniki tych zapytań do osobnej bazy danych tego samego typu / smaku. Załóżmy, że mam wiele wątków roboczych wszystkich zapytań do bazy danych przesłanych do kolejki

#in a long running loop
try:
    query = queue.get()
    conn = connect_to_db(<main db>)
    curs = conn.cursor()
    try:
        curs.execute("<some query on user input that may fail even if sanitized">)
    except DBError:
        logconn = connect_to_db(<logging db>)
        logcurs = logconn.cursor()
        logcurs.execute("<update in DB log with record of failed query")
        logcurs.close()
        logconn.close()
    else:

        #we can't put this in main try block because an error connecting
        #to the logging DB would be indistinguishable from an error in 
        #the mainquery 

        #We can't put this after the whole try: except: finally: block
        #because then we don't know if the query was successful or not

        logconn = connect_to_db(<logging db>)
        logcurs = logconn.cursor()
        logcurs.execute("<update in DB log with record of successful query")
        logcurs.close()
        logconn.close()
        #do something in response to successful query
except DBError:
    #This DBError is because of a problem with the logging database, but 
    #we can't let that crash the whole thread over what might be a
    #temporary network glitch
finally:
    curs.close()
    conn.close()
    #other cleanup if necessary like telling the queue the task is finished

Oczywiście, jeśli potrafisz rozróżnić możliwe wyjątki, które mogą zostać zgłoszone, nie musisz tego używać, ale jeśli kod reagujący na udany fragment kodu może zgłosić ten sam wyjątek, co udany fragment, i nie możesz po prostu puść drugi możliwy wyjątek lub natychmiast powróć po sukcesie (co w moim przypadku zabiłoby wątek), to się przydaje.


1

elseBlok może często występować jako uzupełnienie funkcjonalności, który występuje w każdym exceptbloku.

try:
    test_consistency(valuable_data)
except Except1:
    inconsistency_type = 1
except Except2:
    inconsistency_type = 2
except:
    # Something else is wrong
    raise
else:
    inconsistency_type = 0

"""
Process each individual inconsistency down here instead of
inside the except blocks. Use 0 to mean no inconsistency.
"""

W tym przypadku inconsistency_typeustawiana jest w każdym bloku oprócz bloku, dzięki czemu zachowanie jest uzupełniane w przypadku braku błędu w else.

Oczywiście opisuję to jako wzorzec, który może kiedyś pojawić się w twoim kodzie. W tym konkretnym przypadku po prostu ustawiasz inconsistency_type0 przed tryblokiem.


1

Oto inne miejsce, w którym lubię używać tego wzoru:

 while data in items:
     try
        data = json.loads(data)
     except ValueError as e:
        log error
     else:
        # work on the `data`

1
Zamiast tego możesz użyć continue- wzorca „wcześnie się wydostać”. Pozwala to na usunięcie klauzuli „else” i jej wcięcia, dzięki czemu kod jest łatwiejszy do odczytania.
malthe

1

Jednym ze scenariuszy użycia, który mogę wymyślić, są nieprzewidywalne wyjątki, które można obejść, jeśli spróbujesz ponownie. Na przykład, gdy operacje w bloku try obejmują losowe liczby:

while True:
    try:
        r = random.random()
        some_operation_that_fails_for_specific_r(r)
    except Exception:
        continue
    else:
        break

Ale jeśli można przewidzieć wyjątek, zawsze należy wcześniej wybrać walidację zamiast wyjątku. Jednak nie wszystko można przewidzieć, więc ten wzorzec kodu ma swoje miejsce.


1
Możesz to zrobić umieszczając breakwnętrze tryna końcu, co jest czystszym IMO, i nie potrzebujesz else. Również continuenie jest tak naprawdę potrzebne, możesz po prostu pass.
Dirbaio,

1

Uznałem, że elseprzydatne jest postępowanie z potencjalnie niepoprawnym plikiem konfiguracyjnym:

try:
    value, unit = cfg['lock'].split()
except ValueError:
    msg = 'lock monitoring config must consist of two words separated by white space'
    self.log('warn', msg)
else:
     # get on with lock monitoring if config is ok

Wyjątek czytający lockconfig wyłącza monitorowanie blokady, a ValueErrors rejestruje pomocny komunikat ostrzegawczy.


1

Załóżmy, że logika programowania zależy od tego, czy słownik zawiera wpis z danym kluczem. Możesz przetestować wynik dict.get(key)użycia if... else...konstruktu lub możesz:

try:
    val = dic[key]
except KeyError:
    do_some_stuff()
else:
    do_some_stuff_with_val(val)

-1

Dodałbym inny przypadek użycia, który wydaje się prosty podczas obsługi sesji DB:

    # getting a DB connection 
    conn = db.engine.connect()

    # and binding to a DB session
    session = db.get_session(bind=conn)

    try:
        # we build the query to DB
        q = session.query(MyTable).filter(MyTable.col1 == 'query_val')

        # i.e retrieve one row
        data_set = q.one_or_none()

        # return results
        return [{'col1': data_set.col1, 'col2': data_set.col2, ...}]

    except:
        # here we make sure to rollback the transaction, 
        # handy when we update stuff into DB
        session.rollback()
        raise

    else:
        # when no errors then we can commit DB changes
        session.commit()

    finally:
        # and finally we can close the session
        session.close()

-17

else:Blok jest mylące i (prawie) bezużyteczne. Jest to również część instrukcji fori while.

W rzeczywistości, nawet na podstawie oświadczenia if, else:można je nadużywać w naprawdę okropny sposób, tworząc bardzo trudne do znalezienia błędy.

Rozważ to.

   if a < 10:
       # condition stated explicitly
   elif a > 10 and b < 10:
       # condition confusing but at least explicit
   else:
       # Exactly what is true here?
       # Can be hard to reason out what condition is true

Pomyśl dwa razy else:. To generalnie problem. Unikaj tego, z wyjątkiem instrukcji if-state, a nawet rozważ udokumentowanie elsewarunku, aby wyrazić to wyraźnie.


6
Nie zgodziłbym się z tym. W bloku „if-elif”, „else” jest używany jako „domyślny” byłby używany w bloku „case” języka C. Zawsze zaleca się obsługę „domyślnego” przypadku, nawet jeśli uważasz, że objąłeś wszystkie przypadki w różnych warunkach.
Josip

1
@Josip: używane jako „domyślne” może być mylące. Problem polega na jasnym zdefiniowaniu warunku, który jest tym „domyślnym”. Źle zdefiniowany warunek domyślny może być główną przyczyną błędów w działaniu. W przeciwnym razie może dojść do zamieszania. Należy to przemyśleć bardzo ostrożnie we wszystkich przypadkach, nie tylko próbować, na jakiś czas, ale jeśli także.
S.Lott

5
Cóż, powyższy kod jest całkowicie abstrakcyjny i nie robi nic znaczącego, więc tak - nic dziwnego, że jest mylący.
julx

1
@ S.Lott „Zmniejszyłoby to błędy” - i mam na myśli to, że to nieprawda. Myślę, że mamy prawdziwą różnicę w opiniach. Źli programiści zawsze znajdują sposoby na pisanie błędnych programów. Zawsze. Dobrzy programiści zawsze szukają dobrych praktyk i potrafią pisać dobry kod w prawie każdym języku. Wyeliminowanie użytecznych konstrukcji daje po prostu mniej mocy dobrym programistom, ale nie pomaga szczególnie złym, ponieważ są oni w stanie wymyślić nieskończoną liczbę sposobów, aby sfałszować rzeczy.
julx

5
Zastanów się: if x > 0: return "yes"i if x <= 0: return "no". Teraz ktoś przychodzi i zmienia jeden z warunków, x > 1ale zapomina zmienić drugi. Jaka jest ta redukcja liczby błędów, które zostałyby popełnione. if elseklauzule mają czasem wiele wierszy od siebie. SUSZENIE to dobra praktyka, o wiele częściej niż nie. (przepraszam za podwójny post).
julx
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.