2012-10-25 49 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__'中跳過'''body'(參見[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__成功時不想吞下例外,請在__exit__return False中檢查self.enter_ok,如果它是True

+1

If '__enter__'中有一個例外,你可以調用'__exit__',有什麼方法可以打破客戶端代碼中的'with'模塊? – tMC

+0

@tMC查看已更新的答案。 –

+0

大聲笑我只是在同一時間想到這一點。我用相同的邏輯更新了我的問題。 – tMC

6
最好的設計決策

不可以。如果在__enter__()有可能發生異常,那麼您需要自己動手,並打電話給幫手包含清理代碼的函數。

2

你可以使用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 

或者從the docs一個例子:

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

contextlib2 on Python <3.3

1

如果不需要繼承或複雜的子程序,你可以使用更短的方式:

from contextlib import contextmanager 

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

是的,但是這會在contextlib中拋出「generator does not yield」。 – georg

+0

@ thg435,合理,但我們可以用'嘗試'包裝'收益'...終於 – newtover

+0

這是在一個最終塊產量 – 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 

我已經做到了這個樣子。它以錯誤作爲參數調用__exit __()。如果args [0]包含錯誤,它會在執行清理代碼後重新渲染異常。