Czy dla każdego możliwego bloku try-Final w Pythonie jest zagwarantowane, że finally
blok będzie zawsze wykonywany?
Na przykład, powiedzmy, że wracam będąc w except
bloku:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
A może przebijam ponownie Exception
:
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
Testy pokazują, że finally
tak się dzieje w powyższych przykładach, ale wyobrażam sobie, że istnieją inne scenariusze, o których nie pomyślałem.
Czy istnieją scenariusze, w których finally
blok może nie zostać wykonany w Pythonie?
python
exception-handling
try-catch-finally
finally
Stevoisiak
źródło
źródło
finally
niepowodzenie w wykonaniu lub „pokonaniu jego celu”, to nieskończona pętlasys.exit
lub wymuszone przerwanie. W dokumentacji stwierdza, żefinally
zawsze jest tak wykonana, że pójdę z tym.finally
nie zadziała. Albo tak samo, jeśli komputer się wcześniej zawiesił: Dfinally
nie zadziała, jeśli przewód zasilający zostanie wyrwany ze ściany.Odpowiedzi:
„Gwarantowane” to o wiele mocniejsze słowo, niż jakakolwiek realizacja na to
finally
zasługuje. Gwarantowane jest to, że jeśli wykonanie wypłynie z całościtry
-finally
construct, przejdzie przez to,finally
aby to zrobić. Nie można zagwarantować, że wykonanie wypłynie ztry
-finally
.A
finally
w generatorze lub programie asynchronicznym może nigdy nie działać , jeśli obiekt nigdy nie zostanie zakończony. Może się to zdarzyć na wiele sposobów; Tu jest jeden:Zauważ, że ten przykład jest trochę skomplikowany: kiedy generator jest zbierany jako śmieci, Python próbuje uruchomić
finally
blok, rzucającGeneratorExit
wyjątek, ale tutaj łapiemy ten wyjątek iyield
znowu, w którym to momencie Python wyświetla ostrzeżenie ("generator zignorowany GeneratorExit ”) i poddaje się. Aby uzyskać szczegółowe informacje, patrz PEP 342 (Coroutines via Enhanced Generators) .Inne sposoby, w jakie generator lub procedura mogą nie wykonać, aby wyciągnąć wnioski, obejmują to, czy obiekt nigdy nie został poddany GC (tak, jest to możliwe, nawet w CPythonie), lub jeśli
async with
await
jest w__aexit__
lub jeśli obiektawait
jest lubyield
jest wfinally
bloku. Ta lista nie jest wyczerpująca.A
finally
w wątku demona może nigdy nie zostać wykonany, jeśli wszystkie wątki inne niż demonowe zakończą się jako pierwsze.os._exit
natychmiast zatrzyma proces bez wykonywaniafinally
bloków.os.fork
może spowodować dwukrotne wykonaniefinally
bloków . Oprócz zwykłych problemów, których można się spodziewać po zdarzeniach podwójnych, może to powodować współbieżne konflikty dostępu (awarie, blokady, ...), jeśli dostęp do współdzielonych zasobów nie jest poprawnie zsynchronizowany .Ponieważ
multiprocessing
używa fork-without-exec do tworzenia procesów roboczych podczas korzystania z metody fork start (domyślna w systemie Unix), a następnie wywołujeos._exit
proces roboczy po zakończeniu pracy pracownika,finally
amultiprocessing
interakcja może być problematyczna ( przykład ).finally
uruchomienie bloków.kill -SIGKILL
zapobiegniefinally
uruchamianiu bloków.SIGTERM
aSIGHUP
także uniemożliwifinally
uruchamianie bloków, chyba że zainstalujesz program obsługi, który sam kontroluje zamknięcie; domyślnie Python nie obsługujeSIGTERM
aniSIGHUP
.finally
może uniemożliwić ukończenie czyszczenia. Szczególnie godny uwagi jest przypadek, gdy użytkownik kliknie Ctrl-C tylko jako zaczynamy wykonaćfinally
blok. Python podniesie aKeyboardInterrupt
i pominie każdy wierszfinally
zawartości bloku. (KeyboardInterrupt
-bezpieczny kod jest bardzo trudny do napisania).finally
bloki nie będą działać.finally
Blok nie jest system transakcyjny; nie zapewnia gwarancji atomowości ani niczego w tym rodzaju. Niektóre z tych przykładów mogą wydawać się oczywiste, ale łatwo jest zapomnieć, że takie rzeczy mogą się zdarzyć i polegać nafinally
zbyt dużym stopniu.źródło
except
i nigdy nie łapajGeneratorExit
w generatorze. Spodziewane są punkty dotyczące wątków / zabijania procesu / segfaultingu / wyłączania zasilania, Python nie może robić magii. Ponadto: wyjątki w programiefinally
są oczywiście problemem, ale nie zmienia to faktu, że przepływ sterowania został przeniesiony dofinally
bloku. Jeśli chodzi o toCtrl+C
, możesz dodać procedurę obsługi sygnału, która go ignoruje lub po prostu „planuje” czyste zamknięcie po zakończeniu bieżącej operacji.kill -9
nie określa języka. I szczerze mówiąc, wymaga powtórzenia, ponieważ znajduje się w martwym punkcie. Zbyt wiele osób zapomina lub nie zdaje sobie sprawy, że ich program może zostać zatrzymany bez pozwolenia nawet na wyczyszczenie.finally
blokach tak, jakby udzielali gwarancji transakcyjnych. Może się wydawać oczywiste, że tak nie jest, ale nie wszyscy zdają sobie z tego sprawę. Jeśli chodzi o obudowę generatora, istnieje wiele sposobów, w jakie generator może w ogóle nie zostać poddany GC, i wiele sposobów, w jakie generator lub program może przypadkowo ustąpić,GeneratorExit
nawet jeśli nie łapieGeneratorExit
, na przykład jeśliasync with
zawiesza się program w__exit__
.Tak. Wreszcie zawsze wygrywa.
Jedynym sposobem na pokonanie tego jest zatrzymanie wykonywania, zanim
finally:
będzie można je wykonać (np. Zawieszenie interpretera, wyłączenie komputera, zawieszenie generatora na zawsze).Oto kilka innych, o których być może nie pomyślałeś:
W zależności od tego, jak zrezygnowałeś z tłumaczenia, czasami możesz ostatecznie „anulować”, ale nie w ten sposób:
Używanie precarious
os._exit
(moim zdaniem zalicza się to do „crash the interpreter”):Obecnie uruchamiam ten kod, aby sprawdzić, czy w końcu nadal będzie działać po śmierci wszechświata na gorąco:
Jednak wciąż czekam na wynik, więc wróć tutaj później.
źródło
finally
w generatorze lub programie może z łatwością zakończyć się niepowodzeniem , bez zbliżania się do stanu „zawieszenie interpretera”.sleep(1)
z pewnością skutkowałoby nieokreślonym zachowaniem. :-Dos._exit
ze wszystkich praktycznych względów jest tym samym, co spowodowanie awarii (nieczyste wyjście). Prawidłowy sposób wyjścia tosys.exit
.Zgodnie z dokumentacją Pythona :
Należy również zauważyć, że jeśli istnieje wiele instrukcji powrotu, w tym jedna w bloku final, to jedyny, który zostanie wykonany, będzie zwracany ostatni blok.
źródło
Cóż, tak i nie.
Gwarantowane jest to, że Python zawsze będzie próbował wykonać ostatni blok. W przypadku powrotu z bloku lub zgłoszenia nieprzechwyconego wyjątku, ostatni blok jest wykonywany tuż przed faktycznym zwróceniem lub podniesieniem wyjątku.
(co mogłeś kontrolować, uruchamiając kod w swoim pytaniu)
Jedyny przypadek, jaki mogę sobie wyobrazić, w którym ostatni blok nie zostanie wykonany, to sytuacja, w której sam interpreter Pythona ulega awarii, na przykład w kodzie C lub z powodu przerwy w zasilaniu.
źródło
Znalazłem ten bez użycia funkcji generatora:
Uśpienie może być dowolnym kodem, który może działać przez niespójne okresy czasu.
Wydaje się, że dzieje się tutaj to, że pierwszy równoległy proces kończący pomyślnie opuszcza blok try, a następnie próbuje zwrócić z funkcji wartość (foo), która nie została nigdzie zdefiniowana, co powoduje wyjątek. Ten wyjątek zabija mapę, nie pozwalając innym procesom dotrzeć do ich ostatecznych bloków.
Ponadto, jeśli dodasz linię
bar = bazz
tuż po wywołaniu funkcji sleep () w bloku try. Następnie pierwszy proces osiągający tę linię zgłasza wyjątek (ponieważ bazz nie jest zdefiniowany), który powoduje uruchomienie własnego bloku końcowego, ale następnie zabija mapę, powodując, że inne bloki próbne znikają bez dotarcia do ich ostatecznych bloków, i również pierwszy proces, aby nie dotrzeć do instrukcji return.W przypadku wieloprocesorowości Pythona oznacza to, że nie można ufać mechanizmowi obsługi wyjątków w zakresie czyszczenia zasobów we wszystkich procesach, jeśli nawet jeden z procesów może mieć wyjątek. Konieczna byłaby dodatkowa obsługa sygnału lub zarządzanie zasobami poza wywołaniem mapy wieloprocesowej.
źródło
Dodatek do zaakceptowanej odpowiedzi, aby pomóc zobaczyć, jak to działa, z kilkoma przykładami:
To:
wyjdzie
wyjdzie
źródło