在做檔案IO操作時,有一個with語句,他能夠自動的對檔案進行打開和關閉操作。
with open('d:\\abc.txt', 'w') as f:
f.write('hello world')
複制
With有這個特效,其實背後實際上是基于__enter__和__exit__這兩個魔術方法來實作的。一個對象實作了__enter__和__exit__這兩個魔術方法後,也能使用with文法。舉個栗子,把大象放進冰箱有三步操作,第一步打開冰箱,第二步把大象放進冰箱,第三步關上冰箱,如果使用with文法可以這樣實作:
class Elephant(object):
def __init__(self):
pass
def __enter__(self):
print('open ice box')
def __exit__(self, exc_type, exc_val, exc_tb):
print('close ice box')
with Elephant():
print('put elephant into ice box')
輸出:
open ice box
put elephant into ice box
close ice box
複制
上下文管理器(context manager)
前面說with的IO操作特效是基于__enter__和__exit__這兩個魔術方法來實作的,其實更專業的說法,這叫做上下文管理器。
上下文管理器是指在一段代碼執行之前執行一段代碼,用于一些預處理工作;執行之後再執行一段代碼,用于一些清理工作。比如打開檔案進行讀寫,讀寫完之後需要将檔案關閉。在上下文管理協定中,有兩個方法__enter__和__exit__,分别實作上述兩個功能。
使用with文法格式是這樣的:
with EXPR as VAR:
BLOCK
複制
整個執行流程:
- 執行expr語句,獲得上下文管理器
- 調用__enter__方法,做一些預處理操作
- as var 是接收__enter__執行後傳回的結果,如果沒有傳回結果,as var可以省略掉
- 執行block
- 執行__exit__方法,__exit__方法有三個參數,這三個參數是用來處理異常資訊的,exc_type表示異常類型,exc_val表示異常資訊,exc_tb表示異常堆棧資訊。__exit__方法也可以有傳回值,如果為True,那麼表示異常被忽視,相當于進行了try-except操作;如果為False,則該異常會被重新raise。
為了示範效果,在上面那個把大象放入冰箱的例子中人為加一個異常:
class Elephant(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('open ice box')
return self.name
def __exit__(self, exc_type, exc_val, exc_tb):
print('close ice box')
print(exc_type)
print(exc_val)
print(exc_tb)
return True
with Elephant('peter') as name:
print('elephant name: %s'%name)
print('put elephant into ice box')
1/0
輸出:
open ice box
elephant name: peter
put elephant into ice box
close ice box
<class 'ZeroDivisionError'>
division by zero
<traceback object at 0x000001B1E179C148>
複制
__exit__方法預設傳回false,如果傳回true異常會在__exit__内部被消化掉,如果傳回false異常會重新抛出來。
contextlib實作上下文管理器的功能
通過with實作上下文管理器的功能需要實作兩個方法,contextlib庫中有一個contextmanage裝飾器,可以通過這個裝飾器以一種更簡單的方式來實作上下文管理器的功能。
contextmanage中yield之前的代碼類似于__enter__的作用,yield的傳回值指派給as之後的變量,yield之後的代碼類似于_exit__的作用。之前有講過yield放在函數中表示的是生成器,但是如果函數加了contextmanager裝飾器,則該函數就變成了了上下文管理器了。
import contextlib
@contextlib.contextmanager
def elephant(name):
print('open ice box')
yield name
print('close ice box')
with elephant('peter') as name:
print('elephant name: %s'%name)
print('put elephant into ice box')
輸出:
open ice box
elephant name: peter
put elephant into ice box
close ice box
複制
和with不同的是,contextmanage實作的上下文管理器沒有對異常做捕獲,需要自己處理。
上下文管理器的應用
自定義一個檔案io操作的上下文管理器。
class FileOpener(object):
def __init__(self, filename, filemode):
self.filename = filename
self.filemode = filemode
def __enter__(self):
self.fp = open(self.filename, self.filemode)
return self.fp
def __exit__(self, exc_type, exc_val, exc_tb):
self.fp.close()
with FileOpener('test.txt', 'w') as fp:
fp.write('hello')
複制
本人是做大資料開發的,在微信上開了個個人号,會經常在上面分享一些學習心得,原創文章都會首發到公衆号上,感興趣的盆友可以關注下哦!
大資料入坑指南