29

__exit__()に例外があってもメソッドが確実に呼び出されるようにすることは可能__enter__()ですか?

>>> class TstContx(object):
...    def __enter__(self):
...        raise Exception('Oops in __enter__')
...
...    def __exit__(self, e_typ, e_val, trcbak):
...        print "This isn't running"
... 
>>> with TstContx():
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __enter__
Exception: Oops in __enter__
>>> 

編集

これは私が得ることができる限り近いです...

class TstContx(object):
    def __enter__(self):
        try:
            # __enter__ code
        except Exception as e
            self.init_exc = e

        return self

    def __exit__(self, e_typ, e_val, trcbak):
        if all((e_typ, e_val, trcbak)):
            raise e_typ, e_val, trcbak

        # __exit__ code


with TstContx() as tc:
    if hasattr(tc, 'init_exc'): raise tc.init_exc

    # code in context

後から考えると、コンテキスト マネージャーは最適な設計上の決定ではなかった可能性があります。

4

7 に答える 7

27

このような:

import sys

class Context(object):
    def __enter__(self):
        try:
            raise Exception("Oops in __enter__")
        except:
            # Swallow exception if __exit__ returns a True value
            if self.__exit__(*sys.exc_info()):
                pass
            else:
                raise


    def __exit__(self, e_typ, e_val, trcbak):
        print "Now it's running"


with Context():
    pass

コンテキストブロックを実行せずにプログラムを陽気な方法で続行するには、コンテキストブロック内のコンテキストオブジェクトを検査し、__enter__成功した場合にのみ重要なことを実行する必要があります。

class Context(object):
    def __init__(self):
        self.enter_ok = True

    def __enter__(self):
        try:
            raise Exception("Oops in __enter__")
        except:
            if self.__exit__(*sys.exc_info()):
                self.enter_ok = False
            else:
                raise
        return self

    def __exit__(self, e_typ, e_val, trcbak):
        print "Now this runs twice"
        return True


with Context() as c:
    if c.enter_ok:
        print "Only runs if enter succeeded"

print "Execution continues"

私が判断できる限り、with-blockを完全にスキップすることはできません。また、このコンテキストは現在、その中のすべての例外を飲み込んでいることに注意してください。成功した場合に例外を飲み込みたくない場合は__enter__、チェックインself.enter_ok__exit__return False、それが成功したかどうかを確認してくださいTrue

于 2012-10-25T18:38:44.537 に答える
12

いいえ。例外が発生する可能性がある場合は、__enter__()自分で例外をキャッチし、クリーンアップ コードを含むヘルパー関数を呼び出す必要があります。

于 2012-10-25T18:31:19.137 に答える
5

RAII (リソースの取得は初期化です) に従い、コンテキストのコンストラクターを使用して、失敗する可能性のある割り当てを行うことをお勧めします。次に__enter__、例外を発生させてはならない自己を単純に返すことができます。コンストラクターが失敗すると、 with コンテキストに入る前に例外がスローされる場合があります。

class Foo:
    def __init__(self):
        print("init")
        raise Exception("booh")

    def __enter__(self):
        print("enter")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        return False


with Foo() as f:
    print("within with")

出力:

init
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  ...
    raise Exception("booh")
Exception: booh

編集: 残念ながら、このアプローチでは、ユーザーが次のようなことをしてもクリーンアップされない「ぶら下がっている」リソースを作成できます。

foo = Foo() # this allocates resource without a with context.
raise ValueError("bla") # foo.__exit__() will never be called.

クラスの新しい実装を変更するか、 with コンテキストなしでオブジェクトのインスタンス化を禁止する他の Python マジックを変更することで、これを回避できるかどうか、非常に興味があります。

于 2019-11-11T17:44:50.570 に答える
3

継承または複雑なサブルーチンが必要ない場合は、より短い方法を使用できます。

from contextlib import contextmanager

@contextmanager
def test_cm():
    try:
        # dangerous code
        yield  
    except Exception, err
        pass # do something
于 2012-10-25T18:55:12.300 に答える
3

使用できますcontextlib.ExitStack(テストされていません):

with ExitStack() as stack:
    cm = TstContx()
    stack.push(cm) # ensure __exit__ is called
    with ctx:
         stack.pop_all() # __enter__ succeeded, don't call __exit__ callback

またはドキュメントの例:

stack = ExitStack()
try:
    x = stack.enter_context(cm)
except Exception:
    # handle __enter__ exception
else:
    with stack:
        # Handle normal case

Python <3.3 の contextlib2 を参照してください。

于 2012-10-25T18:51:15.940 に答える
2
class MyContext:
    def __enter__(self):
        try:
            pass
            # exception-raising code
        except Exception as e:
            self.__exit__(e)

    def __exit__(self, *args):
        # clean up code ...
        if args[0]:
            raise

私はこのようにしました。エラーを引数として __exit__() を呼び出します。args[0] にエラーが含まれている場合、クリーンアップ コードの実行後に例外が再発生します。

于 2016-12-22T12:20:10.863 に答える
1

ドキュメントcontextlib.ExitStackには、クリーンアップを確実にするために使用する例が含まれています。

のドキュメントに記載されているように、このメソッドは、実装ExitStack.push()の後の手順が失敗した場合に、既に割り当てられているリソースをクリーンアップするのに役立ちます。__enter__()

したがってExitStack()、コンテキスト マネージャーをラップするコンテキスト マネージャーとして次のように使用しTstContx()ます。

from contextlib import ExitStack

with ExitStack() as stack:
    ctx = TstContx()
    stack.push(ctx)  # Leaving `stack` now ensures that `ctx.__exit__` gets called.
    with ctx:
        stack.pop_all()  # Since `ctx.__enter__` didn't raise it can handle the cleanup itself.
        ...  # Here goes the body of the actual context manager.
于 2020-11-04T09:57:13.463 に答える