web-dev-qa-db-ja.com

'遂に'は常にPythonで実行されますか?

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ブロックが実行できないシナリオはありますか?

109

「保証」は、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 withawaits in __aexit__、またはawaitブロック内のオブジェクトyieldsまたはfinallysの場合。このリストは完全なものではありません。

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

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

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

    multiprocessingforkstartメソッド (デフォルトを使用する場合、fork-without-execを使用してワーカープロセスを作成するためUnixで)、ワーカーのジョブが完了するとワーカーでos._exitを呼び出します。finallymultiprocessingの相互作用には問題があります( example )。

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

finallyブロックはトランザクションシステムではありません。アトミック性の保証やそのようなものは提供しません。これらの例のいくつかは明白に思えるかもしれませんが、そのようなことが起こりうることを忘れがちであり、finallyに頼りすぎています。

174
user2357112

はい。 ついに勝つ

それを無効にする唯一の方法は、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')

しかし、私はまだ結果を待っているので、後でここでまたチェックしてください。

66
wim

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

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

Finallyブロック内に1つを含む複数のreturn文がある場合は、finallyブロックの戻り値だけが実行されます。

9
jayce

はい、そうです。

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

(あなたの質問の中のコードを実行することによってあなた自身を制御することができたもの)

最後のブロックが実行されないのは、Pythonインタプリタ自体がCコードの内部でクラッシュした場合や停電が原因の場合などです。

8
Serge Ballesta

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

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つのプロセスでも例外が発生する可能性がある場合、すべてのプロセスのリソースをクリーンアップするための例外処理メカニズムを信頼できないということです。多重処理マップ呼び出しの外側で追加の信号処理またはリソースの管理が必要になります。

0
Blair Houghton

それがどのように機能するかを本当に理解するためには、単にこれら二つの例を実行してください:

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

    出力します

    最後に

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

    出力します

    を除く
    最後に

0
Basj