Pythonのtry-finallyブロックで、finally
ブロックが常に実行されることが保証されていますか?
たとえば、except
ブロックに入っている間に戻るとしましょう。
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
あるいは、私はException
を再調達します。
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
テストはfinally
が上記の例のために実行されることを示します、しかし私は私が考えていない他のシナリオがあると思います。
Pythonでfinally
ブロックが実行できないシナリオはありますか?
「保証」は、finally
のどの実装よりもはるかに強力な言葉です。保証されているのは、実行がtry
-finally
コンストラクト全体から流出する場合、finally
を通過することです。保証されないのは、実行がtry
-_finally
から流出することです。
ジェネレーターまたは非同期コルーチンのfinally
は実行されない可能性があります 、オブジェクトが実行されない場合。起こりうる多くの方法があります。ここに一つあります:
def gen(text):
try:
for line in text:
try:
yield int(line)
except:
# Ignore blank lines - but catch too much!
pass
finally:
print('Doing important cleanup')
text = ['1', '', '2', '', '3']
if any(n > 1 for n in gen(text)):
print('Found a number')
print('Oops, no cleanup.')
この例は少し注意が必要です。ジェネレーターがガベージコレクションされると、Pythonはfinally
例外をスローしてGeneratorExit
ブロックを実行しようとしますが、ここでその例外をキャッチしますそしてyield
が再び表示され、その時点でPythonは警告(「ジェネレーターはGeneratorExitを無視しました」)を出力し、givesめます。詳細については、 PEP 342(Enhanced Generators経由のコルーチン) を参照してください。
ジェネレーターまたはコルーチンが結論まで実行されない他の方法には、オブジェクトがGCされない場合(はい、CPythonでも可能です)、またはasync with
await
s in __aexit__
、またはawait
ブロック内のオブジェクトyield
sまたはfinally
sの場合。このリストは完全なものではありません。
デーモンスレッドのfinally
は実行されない可能性があります すべての非デーモンスレッドが最初に終了する場合。
os._exit
はプロセスをすぐに停止しますfinally
ブロックを実行せずに。
os.fork
により、finally
ブロックが executetwice になる場合があります。 2回発生することから予想される通常の問題だけでなく、共有リソースへのアクセスが 正しく同期 でない場合、これにより同時アクセス競合(クラッシュ、ストールなど)が発生する可能性があります。
multiprocessing
は forkstartメソッド (デフォルトを使用する場合、fork-without-execを使用してワーカープロセスを作成するためUnixで)、ワーカーのジョブが完了するとワーカーでos._exit
を呼び出します。finally
とmultiprocessing
の相互作用には問題があります( example )。
finally
ブロックの実行を妨げます。kill -SIGKILL
は、finally
ブロックの実行を防ぎます。 SIGTERM
およびSIGHUP
は、シャットダウンを自分で制御するハンドラーをインストールしない限り、finally
ブロックの実行も防ぎます。デフォルトでは、PythonはSIGTERM
またはSIGHUP
を処理しません。finally
の例外は、クリーンアップの完了を妨げる可能性があります。特に注目すべきケースの1つは、finally
ブロックの実行を開始するときにユーザーがcontrol-Cjustを押した場合です。 PythonはKeyboardInterrupt
を発生させ、finally
ブロックのコンテンツのすべての行をスキップします。 (KeyboardInterrupt
--安全なコードは書くのが非常に難しい)。finally
ブロックは実行されません。finally
ブロックはトランザクションシステムではありません。アトミック性の保証やそのようなものは提供しません。これらの例のいくつかは明白に思えるかもしれませんが、そのようなことが起こりうることを忘れがちであり、finally
に頼りすぎています。
はい。 ついに勝つ
それを無効にする唯一の方法は、finally:
が実行の機会を得る前に実行を停止することです(例えば、インタプリタをクラッシュさせ、コンピュータの電源を切ったり、ジェネレータを永遠に一時停止させたりする)。
私は考えていない他のシナリオがあると思います。
あなたが考えていなかったかもしれないより多くのものがここにあります:
def foo():
# finally always wins
try:
return 1
finally:
return 2
def bar():
# even if he has to eat an unhandled exception, finally wins
try:
raise Exception('boom')
finally:
return 'no boom'
どのようにインタプリタを終了したかによって、最終的に "キャンセル"することができますが、これは好きではありません。
>>> import sys
>>> try:
... sys.exit()
... finally:
... print('finally wins!')
...
finally wins!
$
不安定な os._exit
を使う(これは私の意見では「インタプリタをクラッシュさせる」に該当する):
>>> import os
>>> try:
... os._exit(1)
... finally:
... print('finally!')
...
$
私は現在このコードを実行しています、宇宙の熱死の後についにまだ実行されるかどうかテストするために:
try:
while True:
sleep(1)
finally:
print('done')
しかし、私はまだ結果を待っているので、後でここでまたチェックしてください。
Pythonのドキュメント によると、
以前に何が起こったとしても、コードブロックが完成し、発生した例外が処理されると、最後のブロックが実行されます。例外ハンドラまたはelse-blockにエラーがあって新しい例外が発生したとしても、final-blockのコードは実行されたままです。
Finallyブロック内に1つを含む複数のreturn文がある場合は、finallyブロックの戻り値だけが実行されます。
はい、そうです。
保証されているのは、Pythonが常にfinallyブロックを実行しようとするということです。ブロックから戻ったり、キャッチされていない例外を発生させた場合、finallyブロックは実際に例外を返すか発生させる直前に実行されます。
(あなたの質問の中のコードを実行することによってあなた自身を制御することができたもの)
最後のブロックが実行されないのは、Pythonインタプリタ自体がCコードの内部でクラッシュした場合や停電が原因の場合などです。
ジェネレータ関数を使わずにこれを見つけました:
import multiprocessing
import time
def fun(arg):
try:
print("tried " + str(arg))
time.sleep(arg)
finally:
print("finally cleaned up " + str(arg))
return foo
list = [1, 2, 3]
multiprocessing.Pool().map(fun, list)
Sleepは、矛盾した時間実行される可能性のある任意のコードです。
ここで起こっているのは、最初に終了した並列プロセスがtryブロックを正常に終了した後、どこからも定義されていない値(foo)を関数から戻そうとし、例外を引き起こすことです。その例外は他のプロセスがそれらのfinallyブロックに到達することを許可せずにマップを殺します。
また、tryブロックのsleep()呼び出しの直後にbar = bazz
という行を追加したとします。それから、その行に到達する最初のプロセスは例外を投げ(bazzが定義されていないため)、それはそれ自身のfinallyブロックを実行させますがそれからマップを殺します。 returnステートメントにも到達しない最初のプロセス.
これがPythonのマルチプロセッシングにとって意味することは、1つのプロセスでも例外が発生する可能性がある場合、すべてのプロセスのリソースをクリーンアップするための例外処理メカニズムを信頼できないということです。多重処理マップ呼び出しの外側で追加の信号処理またはリソースの管理が必要になります。
それがどのように機能するかを本当に理解するためには、単にこれら二つの例を実行してください:
try:
1
except:
print 'except'
finally:
print 'finally'
出力します
最後に
try:
1/0
except:
print 'except'
finally:
print 'finally'
出力します
を除く
最後に