2012-10-25 13 views
16

__enter__()に例外があっても__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

問題は、 '__enter__'の中から' with'ボディをスキップすることはできません([pep 377](http://www.python.org/dev/peps/pep-0377/)を参照してください) – georg

答えて

14

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にチェックしてください。

+1

If '__enter__'に例外があり、' __exit__'を呼び出すと、クライアントコードの 'with'ブロックから脱出する方法はありますか? – tMC

+0

@tMC更新された回答を参照してください。 –

+0

lol私はそれについて同時に考えました。私は同じ論理で質問を更新しました。 – tMC

6
されていない可能性があります

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

2

あなたはcontextlib.ExitStack(テストしていません)使用することができますthe docsから

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 

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

from contextlib import contextmanager 

@contextmanager 
def test_cm(): 
    try: 
     # dangerous code 
     yield 
    except Exception, err 
     pass # do something 
1

は、あなたが短い方法を使用することができます。それは、エラーを引数として__exit __()を呼び出します。 args [0]にエラーが含まれていると、クリーンアップコードを実行した後に例外が再生成されます。

+1

はい、これはcontextlibで "generator did yield"を投げます。 – georg

+0

@ thg435、合理的ですが、最終的にブロック – newtover

+0

の最終的な試行で 'yield'をラップすることができますが、回避策はたくさんありますが、問題の根本はスキップすることができないということです'with'ブロック全体。したがって、たとえ何らかの形で「入力」の例外を処理することができたとしても、そのブロックは引き続き実行され、引数として「なし」またはその他のごみが含まれます。 – newtover

0
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 

私はこのようにそれをやった:相続や複雑なサブルーチンが必要とされていない場合