python - 例外 - 複合文とは




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

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は GeneratorExit 例外をスローして finally ブロックを実行しようとしますが、ここではその例外をキャッチしてから再度 yield します。ジェネレータはGeneratorExit ")を無視して諦めます。 詳細は PEP 342(Enhanced Generatorsによるコルーチン) を参照してください。

    他の方法では、オブジェクトがGCされない(CPythonでも可能です)、あるいは __aexit__ の中 async with await sが async with 、あるいはオブジェクトがsまたは yield await 場合などです。 sは finally ブロックに入っています。 このリストは網羅的なものではありません。

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

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

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

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

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

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


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

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

    出力します

    最後に

  • try:
        1/0
    except:
        print 'except'
    finally:
        print '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')

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


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

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()呼び出しの直後にline bar = bazz を追加したとします。 それから、その行に到達する最初のプロセスは例外を投げ(bazzが定義されていないため)、それはそれ自身のfinallyブロックを実行させますがそれからマップを殺し、他のtryブロックはそれらのfinallyブロックに到達せずに消えます。 returnステートメントにも到達しない最初のプロセス

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





finally