2

最近、Python の with ステートメントで奇妙な動作に遭遇しました。__exit__Python のコンテキスト マネージャーを使用してメソッドの構成変更をロールバックするコードがあります。manager は、return Falseの finally ブロックに値を持っていました__exit__。次のコードでケースを分離しました - 唯一の違いは return ステートメントのインデントにあります:

class Manager1(object):

    def release(self):
        pass # Implementation not important

    def rollback(self):
        # Rollback fails throwing an exception:
        raise Exception("A failure")

    def __enter__(self):
        print "ENTER1"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print "EXIT1"
        try:
            self.rollback()
        finally:
            self.release()
            return False          # The only difference here!


class Manager2(object):

    def release(self):
        pass # Implementation not important

    def rollback(self):
        # Rollback fails throwing an exception:
        raise Exception("A failure")

    def __enter__(self):
        print "ENTER2"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print "EXIT2"
        try:
            self.rollback()
        finally:
            self.release()
        return False      # The only difference here!

上記のコードでは、ロールバックは例外で失敗します。私の質問は、なぜManager1が とは異なる動作をするかということですManager2。例外は with-statement の外側ではスローされずManager1、なぜ の終了時に例外がスローされるのかManager2

with Manager1() as m:          
    pass                  # The Exception is NOT thrown on exit here


with Manager2() as m:
    pass                  # The Exception IS thrown on exit here

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

例外が提供され、メソッドが例外を抑制したい場合 (つまり、例外が伝播されないようにしたい場合)、真の値を返す必要があります。それ以外の場合、例外はこのメソッドの終了時に通常どおり処理されます。

私の意見では、どちらの場合も出口は True を返さないため、両方の場合で例外を抑制すべきではありません。ただし、Manager1 ではそうです。誰もそれを説明できますか?

Python 2.7.6 を使用しています。

4

2 に答える 2

4

finally句がアクティブ化されている場合、ブロックtryが正常に完了したか、処理されたエラーが発生したか、ブロックが をtry実行したことを意味しますreturn

Manager1 ではreturn、句の一部としてステートメントを実行すると、ステートメントがfinally正常に終了し、 が返されFalseます。Manager2 クラスでは、finally 句は引き続き実行されますが、例外が発生した結果として実行された場合、その例外がキャッチされるまで (またはトレースバックでプログラムを終了するまで) コール チェーンを伝播することを停止することはありません。

Manager2.__exit__()例外が発生しない場合にのみ False を返します。

于 2014-07-03T11:34:05.810 に答える
1

これを理解する良い方法は、すべてのコンテキストマネージャーのものから独立した別の例を見ることだと思います:

>>> def test ():
        try:
            print('Before raise')
            raise Exception()
            print('After raise')
        finally:
            print('In finally')
        print('Outside of try/finally')

>>> test()
Before raise
In finally
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    test()
  File "<pyshell#6>", line 4, in test
    raise Exception()
Exception

tryしたがって、ブロック内で例外がスローされると、例外のfinallyのすべてのコードが実行され、ブロック内のすべてのコードが実行されることがわかります。それ以外はすべてスキップされます。これは、スローされる例外によって関数呼び出しが終了するためです。ただし、ブロック内で例外がスローされるためtry、それぞれのfinallyブロックが実行される最後のチャンスがあります。

ここで、関数内の行をコメント アウトするraiseと、関数が途中で終了しないため、すべてのコードが実行されることがわかります。

于 2014-07-03T11:57:30.153 に答える