什么是上下文?其實(shí)我們可以簡單地把它理解成環(huán)境。從一篇文章中抽出一句話,讓你來理解,我們會說這是斷章取義。為什么?因?yàn)槲覀儔焊蜎]考慮到這句話的上下文是什么。編程中的上下文也與此類似,比如『進(jìn)程上下文』,指的是一個進(jìn)程在執(zhí)行的時候,CPU 的所有寄存器中的值、進(jìn)程的狀態(tài)以及堆棧上的內(nèi)容等,當(dāng)系統(tǒng)需要切換到其他進(jìn)程時,系統(tǒng)會保留當(dāng)前進(jìn)程的上下文,也就是運(yùn)行時的環(huán)境,以便再次執(zhí)行該進(jìn)程。
迭代器有迭代器協(xié)議(Iterator Protocol),上下文管理器(Context manager)也有上下文管理協(xié)議(Context Management Protocol)。
__enter__()
和 __exit__()
方法。__enter__()
和 __exit__()
方法。這里先構(gòu)造一個簡單的上下文管理器的例子,以理解 __enter__()
和 __exit__()
方法。
from math import sqrt, pow
class Point(object):
def __init__(self, x, y):
print 'initialize x and y'
self.x, self.y = x, y
def __enter__(self):
print "Entering context"
return self
def __exit__(self, type, value, traceback):
print "Exiting context"
def get_distance(self):
distance = sqrt(pow(self.x, 2) + pow(self.y, 2))
return distance
上面的代碼定義了一個 Point
類,并實(shí)現(xiàn)了 __enter__()
和 __exit__()
方法,我們還定義了 get_distance
方法,用于返回點(diǎn)到原點(diǎn)的距離。
通常,我們使用 with
語句調(diào)用上下文管理器:
with Point(3, 4) as pt:
print 'distance: ', pt.get_distance()
# output
initialize x and y # 調(diào)用了 __init__ 方法
Entering context # 調(diào)用了 __enter__ 方法
distance: 5.0 # 調(diào)用了 get_distance 方法
Exiting context # 調(diào)用了 __exit__ 方法
上面的 with
語句執(zhí)行過程如下:
__enter__()
方法,并將 __enter__()
方法的返回值賦給 as 字句中的變量 pt;__exit__()
方法。__exit__()
方法負(fù)責(zé)執(zhí)行『清理』工作,如釋放資源,關(guān)閉文件等。如果執(zhí)行過程沒有出現(xiàn)異常,或者語句體中執(zhí)行了語句 break/continue/return,則以 None 作為參數(shù)調(diào)用 __exit__(None, None, None)
;如果執(zhí)行過程中出現(xiàn)異常,則使用 sys.exc_info
得到的異常信息為參數(shù)調(diào)用 __exit__(exc_type, exc_value, exc_traceback)
;__exit__(type, value, traceback)
返回 False 或 None,則會重新拋出異常,讓 with 之外的語句邏輯來處理異常;如果返回 True,則忽略異常,不再對異常進(jìn)行處理;上面的 with 語句執(zhí)行過程沒有出現(xiàn)異常,我們再來看出現(xiàn)異常的情形:
with Point(3, 4) as pt:
pt.get_length() # 訪問了對象不存在的方法
# output
initialize x and y
Entering context
Exiting context
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-216-ab4a0e6b6b4a> in <module>()
1 with Point(3, 4) as pt:
----> 2 pt.get_length()
AttributeError: 'Point' object has no attribute 'get_length'
在我們的例子中,__exit__
方法返回的是 None(如果沒有 return 語句那么方法會返回 None)。因此,with 語句拋出了那個異常。我們對 __exit__
方法做一些改動,讓它返回 True。
from math import sqrt, pow
class Point(object):
def __init__(self, x, y):
print 'initialize x and y'
self.x, self.y = x, y
def __enter__(self):
print "Entering context"
return self
def __exit__(self, type, value, traceback):
print "Exception has been handled"
print "Exiting context"
return True
def get_distance(self):
distance = sqrt(pow(self.x, 2) + pow(self.y,2 ))
return distance
with Point(3, 4) as pt:
pt.get_length() # 訪問了對象不存在的方法
# output
initialize x and y
Entering context
Exception has been handled
Exiting context
可以看到,由于 __exit__
方法返回了 True,因此沒有異常會被 with 語句拋出。
除了自定義上下文管理器,Python 中也提供了一些內(nèi)置對象,可直接用于 with 語句中,比如最常見的文件操作。
傳統(tǒng)的文件操作經(jīng)常使用 try/finally
的方式,比如:
file = open('somefile', 'r')
try:
for line in file:
print line
finally:
file.close() # 確保關(guān)閉文件
將上面的代碼改用 with 語句:
with open('somefile', 'r') as file:
for line in file:
print line
可以看到,通過使用 with,代碼變得很簡潔,而且即使處理過程發(fā)生異常,with 語句也會確保我們的文件被關(guān)閉。
除了在類中定義 __enter__
和 __exit__
方法來實(shí)現(xiàn)上下文管理器,我們還可以通過生成器函數(shù)(也就是帶有 yield 的函數(shù))結(jié)合裝飾器來實(shí)現(xiàn)上下文管理器,Python 中自帶的 contextlib 模塊就是做這個的。
contextlib 模塊提供了三個對象:裝飾器 contextmanager、函數(shù) nested 和上下文管理器 closing。其中,contextmanager 是一個裝飾器,用于裝飾生成器函數(shù),并返回一個上下文管理器。需要注意的是,被裝飾的生成器函數(shù)只能產(chǎn)生一個值,否則會產(chǎn)生 RuntimeError 異常。
下面我們看一個簡單的例子:
from contextlib import contextmanager
@contextmanager
def point(x, y):
print 'before yield'
yield x * x + y * y
print 'after yield'
with point(3, 4) as value:
print 'value is: %s' % value
# output
before yield
value is: 25
after yield
可以看到,yield 產(chǎn)生的值賦給了 as 子句中的 value 變量。
另外,需要強(qiáng)調(diào)的是,雖然通過使用 contextmanager 裝飾器,我們可以不必再編寫 __enter__
和 __exit__
方法,但是『獲取』和『清理』資源的操作仍需要我們自己編寫:『獲取』資源的操作定義在 yield 語句之前,『釋放』資源的操作定義在 yield 語句之后。
__enter__
和 __exit__
方法。with
語句調(diào)用上下文管理器。with 語句尤其適用于對資源進(jìn)行訪問的場景,確保執(zhí)行過程中出現(xiàn)異常情況時也可以對資源進(jìn)行回收,比如自動關(guān)閉文件等。__enter__
方法在 with 語句體執(zhí)行前調(diào)用,with 語句將該方法的返回值賦給 as 字句中的變量,如果有 as 字句的話。__exit__
方法在退出運(yùn)行時上下文
時被調(diào)用,它負(fù)責(zé)執(zhí)行『清理』工作,比如關(guān)閉文件,釋放資源等。如果退出時沒有發(fā)生異常,則 __exit__
的三個參數(shù),即 type, value 和 traceback 都為 None。如果發(fā)生異常,返回 True 表示不處理異常,否則會在退出該方法后重新拋出異常以由 with 語句之外的代碼邏輯進(jìn)行處理。