從with關鍵字到編寫自己簡單的ContextManager(一)

本文先介紹with表達式,然後再試圖用with以及裝飾器等知識實現自己的ContextManager

with可以幹什麼?我的理解是簡化try except finally的工作,比如打開文件操作符,讀文件,捕捉異常,最後關閉。這個例子是with最最常用的方法了,滿大街都可以找到這個例子。
除文件open操作之外,其實其它很多操作也可以掐頭去尾,留下中間關鍵操作就行。
那麼該如何實現呢?按照python文檔解釋,只要實現__enter__和__exit__兩個函數就可以。
很簡單:
class ConMgr(object):
def __init__(self):
print("__init__ called")


def __call__(self, *args):
print("__call__ called")
#: 可以把傳進來的參數保存着,在with開始時運行
self.args = args
return self


def __enter__(self):
# 打印之前傳進來的參數
print("__enter__ called", self.args)
return 'abcd'


def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__ called: exc_type = %s exc_val = %s exc_tb = %s "\
% (exc_type, exc_val, exc_tb))
return "exited"


def test1():
c = ConMgr()
with c('pppp') as tmp:
print(tmp)
print("haha")
assert 1>2
print(3)

test1()

輸出結果是:
__init__ called
__call__ called
('__enter__ called', ('pppp',))
abcd
haha
__exit__ called: exc_type = <type 'exceptions.AssertionError'> exc_val = exc_tb = <traceback object at 0x7f911003b128>

首先請注意,這裏的c()是類的一個實例,就是一個普通類,而不是generator。當c('pppp')執行時,調用了__call__函數,__call__函數趕緊把傳入的參數保存了下來,等進了with塊之後,調用__enter__之時再把參數放出來。
另外__call__函數一定要返回self,因爲with塊運行完了之後,將會調用self.__exit__()如果不返回self將找不到__exit__函數。
然後就是as語句的tmp值實際上是__enter__的返回值,返回什麼都可以,無所謂的,哪怕傳個閉包。這裏的好主意是把之前的args可以傳給tmp。
最後請仔細看,當assert 1>2發生錯誤之後,with塊沒有執行完就調用__exit__函數了。通過這個函數的參數,我們來實現異常處理。

接下來介紹下python的contextlib這個模塊。
可能有朋友不知到,這個模塊沒有主軸功能,主要是圍繞with語句,提供了一些方便的util函數操作。
這個模塊裏面有一個contextmanager的裝飾器,它可以省掉我們之前那麼麻煩創建一個class然後補上__enter__和__exit__的過程,它利用工廠模式生成一個generator,然後就可以方便的使用with語句了。
關於官方contextlib模塊裏面的功能,我想自己能不能做一個山寨版出來,詳細見[url=http://luozhaoyu.iteye.com/blog/1512917]下文[/url]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章