Dlaczego potrzebujemy klauzuli „wreszcie” w Pythonie?

306

Nie jestem pewien, dlaczego potrzebujemy finallyw try...except...finallyoświadczeniach. Moim zdaniem ten blok kodu

try:
    run_code1()
except TypeError:
    run_code2()
other_code()

jest to samo z tym przy użyciu finally:

try:
    run_code1()
except TypeError:
    run_code2()
finally:
    other_code()

Czy coś brakuje?

RNA
źródło

Odpowiedzi:

422

To robi różnicę, jeśli wrócisz wcześniej:

try:
    run_code1()
except TypeError:
    run_code2()
    return None   # The finally block is run before the method returns
finally:
    other_code()

Porównaj z tym:

try:
    run_code1()
except TypeError:
    run_code2()
    return None   

other_code()  # This doesn't get run if there's an exception.

Inne sytuacje, które mogą powodować różnice:

  • Jeśli wyjątek zostanie zgłoszony w bloku wyjątku.
  • Jeśli zostanie zgłoszony wyjątek, run_code1()ale nie jest toTypeError .
  • Inne instrukcje przepływu kontroli, takie jak continuei break.
Mark Byers
źródło
1
spróbuj: #x = Witaj + 20 x = 10 + 20 z wyjątkiem: wydrukuj „Jestem w bloku oprócz” x = 20 + 30 w innym przypadku: wydrukuj „Jestem w innym bloku” x + = 1 w końcu: wydrukuj „w końcu x =% s '% (x)
Abhijit Sahu
89

Możesz użyć finallydo upewnienia się, że pliki lub zasoby są zamknięte lub zwolnione bez względu na to, czy wystąpi wyjątek, nawet jeśli wyjątek nie zostanie przechwycony. (Lub jeśli nie złapiesz tego konkretnego wyjątku).

myfile = open("test.txt", "w")

try:
    myfile.write("the Answer is: ")
    myfile.write(42)   # raises TypeError, which will be propagated to caller
finally:
    myfile.close()     # will be executed before TypeError is propagated

W tym przykładzie lepiej byłoby użyć withinstrukcji, ale tego rodzaju struktury można użyć do innych rodzajów zasobów.

Kilka lat później napisałem post na blogu o nadużyciach, finallyktóre czytelnicy mogą uznać za zabawne.

kindall
źródło
23

Nie są równoważne. Wreszcie kod jest uruchamiany bez względu na to, co się jeszcze stanie. Jest to przydatne w przypadku kodu czyszczenia, który musi zostać uruchomiony.

Antymon
źródło
15
Finally code is run no matter what else happens... chyba że istnieje nieskończona pętla. Lub Powercut. Lub os._exit(). Lub ...
Mark Byers,
3
@ Mark Właściwie sys.exit zgłasza normalny wyjątek. Ale tak, wszystko, co powoduje natychmiastowe zakończenie procesu, oznacza, że ​​nic innego się nie wykonuje.
Antimony
1
@Antimony: Dzięki. Zmieniono na os._exit.
Mark Byers
Zastanawiam się tylko, dlaczego nie mogę umieścić kodu czyszczącego w wyjątku, jeśli kod zostanie wprowadzony, chyba że zostanie znaleziony wyjątek?
Stephen Jacob
2
@Stephen Po pierwsze, kod działa, nawet jeśli powrócisz z bloku try. W takim przypadku nie trafisz klauzuli oprócz.
Antymon
18

Aby dodać do pozostałych odpowiedzi powyżej, finallyklauzula jest wykonywana bez względu na wszystko, podczas gdy elseklauzula jest wykonywana tylko wtedy, gdy wyjątek nie został zgłoszony.

Na przykład zapis do pliku bez wyjątków spowoduje wyświetlenie następujących danych:

file = open('test.txt', 'w')

try:
    file.write("Testing.")
    print("Writing to file.")
except IOError:
    print("Could not write to file.")
else:
    print("Write successful.")
finally:
    file.close()
    print("File closed.")

WYNIK:

Writing to file.
Write successful.
File closed.

Jeśli istnieje wyjątek, kod wyświetli następujące informacje (zauważ, że umyślny błąd jest spowodowany utrzymywaniem pliku tylko do odczytu.

file = open('test.txt', 'r')

try:
    file.write("Testing.")
    print("Writing to file.")
except IOError:
    print("Could not write to file.")
else:
    print("Write successful.")
finally:
    file.close()
    print("File closed.")

WYNIK:

Could not write to file.
File closed.

Widzimy, że finallyklauzula działa niezależnie od wyjątku. Mam nadzieję że to pomoże.

captainblack
źródło
2
To zadziałałoby, nawet gdybyś nie użył klauzuli „w końcu”, która nie odpowiada na pytanie, ponieważ OP chce poznać różnicę, dobrym przykładem byłby inny błąd niż IOError, aby pokazać, że wreszcie blok klauzuli jest wykonywany przed przekazaniem wyjątku do obiektu wywołującego.
Reda Drissi
2
Nie wiedziałem, że elseto coś. Warto wiedzieć.
mazunki
8

Bloki kodu nie są równoważne. finallyKlauzula będzie również działać, jeśli run_code1()zgłasza wyjątek innego niż TypeError, lub jeśli run_code2()zgłasza wyjątek, podczas gdy other_code()w pierwszej wersji nie będzie działać w takich przypadkach.

Sven Marnach
źródło
7

W pierwszym przykładzie, co się stanie, jeśli run_code1()podniesiony zostanie wyjątek, który nie jest TypeError? ... other_code()nie zostanie wykonany.

Porównaj to z finally:wersją: other_code()gwarantuje się, że zostanie wykonane niezależnie od zgłoszonego wyjątku.

Mahawke
źródło
7

Jak wyjaśniono w dokumentacji , finallyklauzula ma na celu zdefiniowanie działań czyszczących, które należy wykonać w każdych okolicznościach .

Jeśli finallyjest obecny, określa procedurę czyszczenia. try Klauzula jest wykonywany, w tym każdy excepti elseklauzul. Jeśli wyjątek wystąpi w którejkolwiek z klauzul i nie zostanie obsłużony, wyjątek zostanie tymczasowo zapisany. finallyKlauzula jest wykonywany. Jeśli istnieje zapisany wyjątek, jest on ponownie zgłaszany na końcu finally klauzuli.

Przykład:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

Jak widać finallyklauzula jest wykonywana w każdym przypadku. TypeErrorPodniesiony przez podzielenie dwa łańcuchy nie są obsługiwane przez z exceptklauzulą, a zatem ponownie podniesiona pofinally klauzula została wykonana.

W aplikacjach w świecie klauzula „Final” jest przydatna do uwalniania zasobów zewnętrznych (takich jak pliki lub połączenia sieciowe), niezależnie od tego, czy użycie zasobu zakończyło się powodzeniem.

Eugene Yarmash
źródło
4

Idealny przykład jest następujący:

try:
    #x = Hello + 20
    x = 10 + 20 
except:
    print 'I am in except block'
    x = 20 + 30
else:
    print 'I am in else block'
    x += 1
finally:
    print 'Finally x = %s' %(x)
Abhijit Sahu
źródło
3

finallysłuży do definiowania „akcji czyszczenia” . finallyKlauzula jest wykonywany w każdym razie przed opuszczeniem trystwierdzenie, czy wyjątek (nawet jeśli nie poradzić) doszło, czy nie.

Drugi przykład @ Byersa.

kakhkAtion
źródło
2

Wreszcie można go również użyć, gdy chcesz uruchomić „opcjonalny” kod przed uruchomieniem kodu do głównej pracy, a ten opcjonalny kod może się nie powieść z różnych powodów.

W poniższym przykładzie nie wiemy dokładnie, jakie wyjątki store_some_debug_infomogą zostać zgłoszone.

Możemy uruchomić:

try:
  store_some_debug_info()
except Exception:
  pass
do_something_really_important() 

Ale większość drobiazgów narzeka na złapanie zbyt niejasnego wyjątku. Ponieważ wybieramy tylko passbłędy, exceptblok tak naprawdę nie dodaje wartości.

try:
  store_some_debug_info()
finally:
  do_something_really_important()     

Powyższy kod ma taki sam efekt jak pierwszy blok kodu, ale jest bardziej zwięzły.

Brad Johnson
źródło
2

Profesjonalne używanie Delphi przez kilka lat nauczyło mnie, jak w końcu zabezpieczyć moje procedury czyszczenia. Delphi prawie wymusza użycie w końcu do wyczyszczenia wszelkich zasobów utworzonych przed blokiem try, aby nie spowodować przecieku pamięci. Tak też działa Java, Python i Ruby.

resource = create_resource
try:
  use resource
finally:
  resource.cleanup

i zasoby zostaną oczyszczone bez względu na to, co zrobisz między próbą a wreszcie. Ponadto nie zostanie wyczyszczone, jeśli wykonanie nigdy nie dotrze do trybloku. (tj. create_resourcesam zgłasza wyjątek) To sprawia, że ​​Twój kod jest „wyjątkowy bezpieczny”.

Co do tego, dlaczego tak naprawdę potrzebujesz bloku, nie wszystkie języki tego potrzebują. W C ++, w którym automatycznie wywoływano destruktory, które wymuszają czyszczenie, gdy wyjątek rozwija stos. Myślę, że jest to krok w kierunku czystszego kodu w porównaniu do wypróbowania ... w końcu języków.

{    
  type object1;
  smart_pointer<type> object1(new type());
} // destructors are automagically called here in LIFO order so no finally required.
nurettin
źródło
2

Blok try ma tylko jedną obowiązkową klauzulę: Instrukcja try. Klauzule wyjątkiem, else i wreszcie są opcjonalne i oparte na preferencjach użytkownika.

w końcu: zanim Python opuści instrukcję try, uruchomi kod w bloku last w każdych warunkach, nawet jeśli zakończy działanie programu. Np. Jeśli Python napotkał błąd podczas uruchamiania kodu w bloku wyjątku lub w przeciwnym razie, blok ostatecznie będzie nadal wykonywany przed zatrzymaniem programu.

Lawrence Krukrubo
źródło
1
To jest źle. Instrukcja wyjątkiem jest obowiązkowa. - Lucas Azevedo 1 lutego o 12:04 To źle, ponieważ właśnie skompilowałem i uruchomiłem program w języku Python 3.5 z blokiem „wypróbuj”, bez klauzuli „oprócz”.
Rob Tow
2
Próbowałem tego sam i ku mojemu niedowierzaniu klauzula wyjątkowa nie jest obowiązkowa.
captainblack
1

Uruchom te kody Python3, aby zobaczyć potrzebę:

PRZYPADEK 1:

count = 0
while True:
    count += 1
    if count > 3:
        break
    else:
        try:
            x = int(input("Enter your lock number here: "))

            if x == 586:
                print("Your lock has unlocked :)")

                break
            else:
                print("Try again!!")

                continue

        except:
            print("Invalid entry!!")
        finally:
            print("Your Attempts: {}".format(count))

PRZYPADEK 2:

count = 0

while True:
    count += 1
    if count > 3:
        break
    else:
        try:
            x = int(input("Enter your lock number here: "))

            if x == 586:
                print("Your lock has unlocked :)")

                break
            else:
                print("Try again!!")

                continue

        except:
            print("Invalid entry!!")

        print("Your Attempts: {}".format(count))

Wypróbuj następujące dane za każdym razem:

  1. losowe liczby całkowite
  2. poprawny kod, który jest 586 (Wypróbuj to, a otrzymasz odpowiedź)
  3. losowe ciągi

** Na bardzo wczesnym etapie nauki języka Python.

AshPython
źródło
1

Próbowałem uruchomić kod, w którym chciałem czytać arkusze programu Excel. Problem polegał na tym, że jeśli istnieje plik, który nie ma arkusza o nazwie powiedz: SheetSum Nie jestem w stanie przenieść go do lokalizacji błędu !! Kod, który napisałem to:

def read_file(data_file):
    # data_file = '\rr\ex.xlsx'
    sheets = {}
    try:
        print("Reading file: "+data_file)
        sheets['df_1'] = pd.read_excel(open(data_file,'rb'), 'SheetSum')
    except Exception as excpt:
        print("Exception occurred", exc_info=True)
    return sheets

read_file(file)
shutil.move( file, dirpath +'\\processed_files')

Podawanie błędu:

[WinError 32] Proces nie może uzyskać dostępu do pliku, ponieważ jest używany przez inny proces

Musiałem dodać pełny try except with finallyblok i powiedzieć, że finallymuszę zamknąć plik w każdym przypadku, np .:

def read_file(data_file):
    # data_file = '\rr\ex.xlsx'
    sheets = {}
    try:
        print("Reading file: "+data_file)
        sheets_file = open(data_file,'rb')
        sheets['df_1'] = pd.read_excel(sheets_file, 'SheetSum')
    except Exception as excpt:
        print("Exception occurred", exc_info=True)
    finally:
        sheets_file.close()
    return sheets

read_file(file)
shutil.move( file, dirpath +'\\processed_files')

W przeciwnym razie plik nadal pozostaje otwarty w tle.

Jeśli finallyjest obecny, określa procedurę czyszczenia . try Klauzula jest wykonywany, w tym każdy excepti elseklauzul. Jeśli wyjątek wystąpi w którejkolwiek z klauzul i nie zostanie obsłużony, wyjątek zostanie tymczasowo zapisany . finallyKlauzula jest wykonywany. Jeśli istnieje zapisany wyjątek, jest on ponownie zgłaszany na końcu finally klauzuli. Jeśli finallyklauzula wywołuje inny wyjątek, zapisany wyjątek jest ustawiany jako kontekst nowego wyjątku.

.. Więcej tutaj

Saqib Mujtaba
źródło