2014-01-06 23 views
3

我很困惑如何安排Python上下文管理器可以做的所有事情到適當的位置。放在一起的Python上下文管理器:一個謎題

據我瞭解,這有可能進入全面建設上下文管理的要素包括:

  • 答:東西總是發生
  • B:需要對C
  • C語言帶來的準備:創建並建立在上下文中使用的對象X
  • D:在上下文開始之前使用成功建立的X執行一些事情
  • E:將X返回到上下文(供使用)
  • F:結束語與X時一切正常在上下文
  • G的端部:進入上下文
  • 小時,然後應對故障的在C和後果B:應對故障的後果上下文

我想我大致得到這些元素在上下文管理器函數中的每個元素的位置,但對於如何將它們排列在類中完全不知所措。

是否有一個上下文管理器函數和類的模板,顯示這些元素中的每一個都在函數和(尤其是)類中?我在這裏和其他地方查看了很多示例,但沒有發現任何全面的示例,並且許多示例使用實際的代碼,我無法總是映射到上面的每個構建塊。


認爲我基本上明白通過函數來​​實現,當一個上下文管理器的行爲:

from contextlib import contextmanager  
@contextmanager 
def log_file_open(oec_data, build_description, log_dir): 
    # A: Something that always happens 
    try: 
     # B: Some stuff needed to make a_thing 
     a_thing = establish_thing_in_a_way_that_might_fail() # C 
     # D: Some things that happen using a_thing at context start 
     yield a_thing # E 
     # F: Wrap up with a_thing when all is well 
    except: 
     # G: Deal the consequences of failure in try or... 
     # H: Deal the consequences of failure in context 
    finally: 
     # Could F go here instead? 

例如,打開的東西應該在成功打開並寫入到一個文件關閉,但應該清理如果有問題,我可以寫

from contextlib import contextmanager  
@contextmanager 
def log_file_open(oec_data, build_description, log_dir): 
    print('Entering context...') 
    try: 
     usable_file_name = get_some_name() 
     a_thing = open(usable_file_name, mode='w') 
     a_thing.write('Logging context started.') 
     yield a_thing 
     a_thing.write('Logging context ended.') 
    except: 
     a_thing.close() 
     os.remove(a_thing.name) 
     raise 

但我不確定這是對的, nd我很困惑它如何映射到__enter()____exit()__在類中的使用。是(示意性):

def __init__(self): 
    # A: Something that always happens 

def __enter__(self): 
    try: 
     # B: Some stuff needed to make a_thing 
     a_thing = establish_thing_in_a_way_that_might_fail() # C 
     # D: Some things that happen using a_thing at context start 
    except: 
     # G: Deal the consequences of failure in try 
     a_thing = some_appropriate_blank_value 
    finally: 
     return a_thing # E 

def __exit__(self, type, value, traceback): 
     if type is None: 
      # F: Wrap up with a_thing when all is well 
      return True 
     else: 
      # H: Deal the consequences of failure in context 
      return False 

回答

2

你正在混合錯誤處理在上下文本身中生成上下文值和錯誤處理。這是再好不過的寫:

@contextmanager 
def fn(...): 
    value = ...  # A, B, C, D: setup 
    try: 
     yield value # E: pass value to client 
    except:   # or better, finally: 
     ...   # F, H: cleanup 

你知道你只處理源自客戶端代碼異常這種方式,和您簡化清理代碼,你知道,安裝成功。試圖在設置代碼中處理異常通常沒有意義;您不希望客戶端代碼必須處理None上下文值。這意味着,__enter__很簡單:

def __enter__(self): 
    self.value = ... # A, B, C, D: setup 
    return self.value # E: pass value to client 

如果__enter__拋出一個異常,那麼__exit__將不會被調用。

還要注意finallyexcept要好,除非你打算從客戶端代碼中排除異常,這是很少用的。所以__exit__只不過是:

def __exit__(self, type, value, traceback): 
    ...    # F, H: cleanup 
    return False  # don't suppress any exception 
1

我覺得你的理解大都是正確的。上下文管理器是對象,其通過其__enter____exit__方法來管理上下文。所以在__init__中會發生什麼事情在對象的生命中保持真實。 讓我們看一個具體的例子:

class CMan(object): 
    def __init__(self, *parameters): 
     "Creates a new context manager" 
     print "Creating object..." 

    def __enter__(self): 
     "Enters the manager (opening the file)" 
     print "Entering context..." 
     a_thing = self # Or any other relevant value to be used in this context 
     print "Returning %s" % a_thing 
     return a_thing 

    def __exit__(self, type, value, traceback): 
     "Exits the context" 
     if type is None: 
      print "Exiting with no exception -> Wrapping up" 
      return 
     print "Exiting with exception %s" % type 

這將被用作此:

>>> with CMan(1,2,3) as x: 
...  print 1 + 1 
Creating object... 
Entering context... 
Returning <__main__.CMan object at 0x02514F70> 
2 
Exiting with no exception -> Wrapping up 

需要注意的是在飛行中創建對象不是強制性的:

>>> mgr = CMan(1,2,3) 
Creating object... 
>>> with mgr as x: 
...  print 1 + 1 
Entering context... 
Returning <__main__.CMan object at 0x02514F70> 
2 
Exiting with no exception -> Wrapping up 

最後, __exit__的返回值決定是否應該提出異常。如果該值評估爲False(例如False,0,None ...),則會引發任何異常。否則,這意味着上下文管理器已經處理了該異常,並且不需要提出。例如:

>>> class Arithmetic(object): 
...  def __enter__(self): 
...   return self 
...  def __exit__(self, type, value, traceback): 
...   if type == ZeroDivisionError: 
...    print "I dont care -> Ignoring" 
...    return True 
...   else: 
...    print "Unknown error: Panicking !" 
...    return False 

>>> with Arithmetic() as a: 
...  print 1/0 # Divide by 0 
I dont care -> Ignoring 

>>> with Arithmetic() as a: 
...  print 1 + "2" # Type error 
Unknown error: Panicking ! 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
TypeError: unsupported operand type(s) for +: 'int' and 'str' 

需要注意的是在由0錯誤的鴻溝的情況下,作爲__exit__返回True,誤差不傳播。在其他情況下,它退出上下文管理器後引發。你可以把一個呼叫到一個上下文管理器:

>>> with X as x: 
...  f(x) 

等價於:

>>> x = X.__enter__() 
>>> try: 
...  exc = None 
...  f(x)  
... except Exception as e: 
...  exc = e 
... finally: 
...  handled = X.__exit__(exc) 
...  if exc and not handled: 
...   raise exc 

當然,如果將引發異常你的方法__enter____exit__,它應該是處理得當,例如如果生成a_thing可能會失敗。通過查找「Python with statement」,您可以在網上找到大量資源,這通常是您如何引用此模式(儘管上下文管理器確實更加正確)

相關問題