「最終的に」は常にPythonで実行されますか?


126

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、上記の例で実行されることを示していますが、私が考えていない他のシナリオがあると思います。

finallyPythonでブロックの実行が失敗する可能性のあるシナリオはありますか?


18
finally実行に失敗したり「その目的を達成できなかった」と私が想像できる唯一のケースは、無限ループsys.exitまたは強制割り込み中です。ドキュメントの状態finallyは常に実行されたが、私はそれでいいと思います。
Xay

1
少し横向きに考えて、あなたが尋ねたことは確かではありませんが、タスクマネージャーを開いてプロセスを強制終了するfinallyと、実行されないことは確かです。あるいは、同じコンピュータが前にクラッシュした場合:D
アレハンドロ

144
finally電源コードを壁から引き剥がすと実行されません。
user253751 2018年

3
C#に関する同じ質問に対するこの回答に興味があるかもしれません:stackoverflow.com/a/10260233/88656
Eric Lippert

1
空のセマフォでブロックします。信号を送らないでください。できました。
マーティンジェームス

回答:


206

「保証された」は、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 finallyGeneratorExit例外をスローしてブロックを実行しようとしますが、ここではその例外をキャッチしてからyieldもう一度警告を出力します(「ジェネレーターはGeneratorExitを無視しました」とあきらめます。詳細については、PEP 342(拡張ジェネレータによるコルーチン)を参照してください。

    ジェネレータまたはコルーチンが結論を出さずに実行しないその他の方法には、オブジェクトがGCされていない場合(そう、CPythonでも可能)、またはasync with awaitsがinの__aexit__場合、またはオブジェクトがブロックawaitのsまたはyieldsの場合がありますfinally。このリストは完全なものではありません。

  • finallyデーモンスレッド内のA は、すべての非デーモンスレッドが最初に終了すると実行されない場合があります。

  • os._exitfinallyブロックを実行せずにプロセスをすぐに停止します

  • os.forkfinallyブロックが2回実行される可能性があります。共有リソースへのアクセスが正しく同期されていない場合、2度起こることから予期される通常の問題だけでなく、同時アクセスの競合(クラッシュ、ストールなど)が発生する可能性があります。

    以来multiprocessing用途はフォークせずに-execのワーカープロセスを作成するために使用するときフォーク startメソッドその後、(Unixではデフォルト)、および呼び出し、os._exit労働者のジョブが完了すると、作業者に、finallyそしてmultiprocessing相互作用が問題にすることができます()。

  • Cレベルのセグメンテーション違反は、finallyブロックの実行を妨げます。
  • kill -SIGKILLfinallyブロックの実行を防ぎます。SIGTERMまた、自分でシャットダウンを制御するハンドラーをインストールしない限りSIGHUPfinallyブロックの実行を防止します。デフォルトでは、PythonはSIGTERMまたはを処理しませんSIGHUP
  • の例外によりfinally、クリーンアップが完了しない場合があります。特に注目すべきケースの1つは、ブロックの実行を開始した直後にユーザーがControl-Cを押した場合ですfinally。Pythonはa KeyboardInterruptを発生させ、finallyブロックの内容のすべての行をスキップします。(KeyboardInterrupt-safeコードを書くのは非常に難しい)。
  • コンピュータの電源が切れた場合、またはコンピュータが休止状態になってウェイクアップしない場合、finallyブロックは実行されません。

finallyブロックは、トランザクションシステムではありません。原子性の保証などの機能は提供しません。これらの例のいくつかは明白に思えるかもしれませんが、そのようなことが起こる可能性があることを忘れてfinally、あまりにも多くに依存していることは簡単です。


14
私はあなたのリストの最初の点だけが本当に関係があると信じています、そしてそれを避ける簡単な方法があります:1)ベアを決して使用せexceptGeneratorExit、ジェネレータの内部でキャッチしないでください。スレッド/プロセスの強制終了/ segfaulting /電源オフに関するポイントが予想されますが、Pythonは魔法をかけることができません。また、例外finallyは明らかに問題ですが、制御フローfinallyブロック移動されたという事実は変わりません。に関してはCtrl+C、それを無視するシグナルハンドラーを追加するか、現在の操作が完了した後にクリーンシャットダウンを単に「スケジュール」することができます。
ジャコモアルゼッタ

8
kill -9の言及は技術的には正しいですが、少し不公平です。どの言語で書かれたプログラムも、kill -9を受け取ってもコードを実行しません。実際、kill -9を受け取るプログラムはまったくないので、たとえ望んでも何も実行できませんでした。以上がkill -9の要点です。
Tom

10
@Tom:要点kill -9は言語を指定していません。そして率直に言って、死角にあるので、繰り返す必要があります。多くの人々は、プログラムを片付けることさえ許されずにそのトラックで完全に停止することができることを忘れている、または気づいていません。
cHao

5
@GiacomoAlzetta:finallyトランザクションの保証を提供するかのようにブロックに依存している人々がいます。彼らがそうでないことは明白に思われるかもしれませんが、それは誰もが実現するものではありません。ジェネレーターのケースについては、ジェネレーターがまったくGCされない可能性がある多くの方法があります。たとえば、中断された場合など、ジェネレーターまたはコルーチンがをGeneratorExitキャッチしなくても、誤って生成される可能性がある多くの方法があります。のコルーチン。GeneratorExitasync with__exit__
user2357112は、

2
@ user2357112ええ-私は何十年もの間、開発者が終了せずにアプリの起動時に一時ファイルなどをクリーンアップするように努めてきました。いわゆる「片付けと優雅な終了」に依存して、失望と涙を求めています:)
マーティン・ジェームス

69

はい。 最後に常に勝つ。

それを打ち負かす唯一の方法は、実行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')

ただし、まだ結果が出るのを待っているので、後でもう一度確認してください。


5
または、トライキャッチでi有限ループを使用
sapy 2018年

8
finallyジェネレーターまたはコルーチンでは、「インタープリターのクラッシュ」状態に近づくことなく、実行が非常に簡単に失敗する可能性があります。
user2357112はモニカ

27
宇宙の熱死後、時間は存在しなくなりますので、sleep(1)明らかに未定義の振る舞いになります。:-D
デビッドフォアスター

_os.exitは、「コンパイラーをクラッシュさせることが唯一の敗北方法」の直後に言及したいと思うかもしれません。現時点では、最終的に勝利する混合inbeteweenの例です。
Stevoisiak 2018年

2
@StevenVascellaro私はそれは必要だとは思いません- os._exitすべての実用的な目的のために、クラッシュを引き起こす(汚れた終了)と同じです。正しい終了方法はsys.exitです。
WIM

9

Pythonのドキュメントによると:

以前に何が起こったとしても、コードブロックが完了し、発生した例外が処理されると、最終ブロックが実行されます。例外ハンドラまたはelse-blockにエラーがあり、新しい例外が発生した場合でも、final-blockのコードは実行されます。

また、finallyブロック内のステートメントを含む複数のreturnステートメントがある場合、finallyブロックのreturnのみが実行されます。


8

まあ、はい、いいえ。

保証されていることは、Pythonが常にfinallyブロックを実行しようとすることです。ブロックから戻るか、キャッチされない例外を発生させる場合、finallyブロックは、実際に例外を返すか発生させる直前に実行されます。

(質問のコードを実行するだけで自分で制御できたもの)

finallyブロックが実行されないことを私が想像できる唯一のケースは、Pythonインタープリター自体が、たとえばCコード内で、または停電が原因でクラッシュした場合です。


ハハ..またはトライキャッチに無限ループがあります
sapy

「まあ、はい、いいえ」が最も正しいと思います。最後に、常に勝ちます。「常に」は、インタープリターが実行可能であり、「finally:」のコードがまだ利用可能であることを意味します。「wins」は、インタープリターがfinally:ブロックの実行を試み、成功するように定義されています。それは「はい」であり、非常に条件付きです。「いいえ」は、「最終的に」の前にインタープリターが停止する可能性があるすべての方法です。-電源障害、ハードウェア障害、インタープリター向けのkill -9、インタープリター内のエラーまたはインタープリターが依存するコード、インタープリターをハングアップさせる他の方法。そして「最終的に」の中にぶら下がる方法。
Bill IV

1

私はジェネレーター関数を使用せずにこれを見つけました:

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)

スリープは、一貫性のない時間実行される可能性のある任意のコードです。

ここで発生しているように見えるのは、最初に終了する並列プロセスがtryブロックを正常に終了しますが、関数からどこにも定義されていない値(foo)を返そうとするため、例外が発生します。その例外は、他のプロセスが最終的にブロックに到達することを許可せずにマップを強制終了します。

また、bar = bazztryブロックのsleep()呼び出しの直後に行を追加した場合。次に、その行に到達する最初のプロセスが例外をスローします(bazzが定義されていないため)。これにより、独自のfinallyブロックが実行されますが、マップが強制終了され、他のtryブロックは、finallyブロックに到達せずに消えます。 returnステートメントに到達しない最初のプロセス。

これがPythonマルチプロセッシングにとって意味することは、プロセスの1つでも例外が発生する可能性がある場合、すべてのプロセスのリソースをクリーンアップする例外処理メカニズムを信頼できないことです。追加の信号処理またはマルチプロセッシングマップ呼び出しの外部のリソースの管理が必要になります。


-2

いくつかの例を挙げて、それがどのように機能するかを確認するのを助けるために、受け入れられた回答の補遺:

  • この:

     try:
         1
     except:
         print 'except'
     finally:
         print 'finally'

    出力されます

    最後に

  •    try:
           1/0
       except:
           print 'except'
       finally:
           print 'finally'

    出力されます

    除く外
    、最終的


これは質問にまったく答えません。それはちょうどそれがときの例がある作品を
zixuan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.