鍍金池/ 教程/ Python/ 上下文管理器
基礎(chǔ)
itertools
HTTP 服務(wù)
hashlib
閉包
文件和目錄
單元測試
使用 @property
標(biāo)準(zhǔn)模塊
陌生的 metaclass
Base64
進(jìn)程、線程和協(xié)程
讀寫二進(jìn)制文件
匿名函數(shù)
輸入和輸出
Click
元組
字符編碼
partial 函數(shù)
參考資料
collections
協(xié)程
類和實(shí)例
Python 之旅
定制類和魔法方法
常用數(shù)據(jù)類型
繼承和多態(tài)
ThreadLocal
HTTP 協(xié)議簡介
Requests 庫的使用
讀寫文本文件
列表
os 模塊
迭代器 (Iterator)
正則表達(dá)式
集合
上下文管理器
異常處理
你不知道的 super
定義函數(shù)
datetime
資源推薦
字典
slots 魔法
hmac
第三方模塊
進(jìn)程
類方法和靜態(tài)方法
函數(shù)參數(shù)
高階函數(shù)
函數(shù)
re 模塊
高級特性
線程
argparse
生成器
結(jié)束語
字符串
map/reduce/filter
函數(shù)式編程
Celery
裝飾器

上下文管理器

什么是上下文?其實(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)。

  • 上下文管理器協(xié)議,是指要實(shí)現(xiàn)對象的 __enter__()__exit__() 方法。
  • 上下文管理器也就是支持上下文管理器協(xié)議的對象,也就是實(shí)現(xiàn)了 __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í)行過程如下:

  • Point(3, 4) 生成了一個上下文管理器;
  • 調(diào)用上下文管理器的 __enter__() 方法,并將 __enter__() 方法的返回值賦給 as 字句中的變量 pt;
  • 執(zhí)行語句體(指 with 語句包裹起來的代碼塊)內(nèi)容,輸出 distance;
  • 不管執(zhí)行過程中是否發(fā)生異常,都執(zhí)行上下文管理器的 __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)
  • 出現(xiàn)異常時,如果 __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 語句拋出。

內(nèi)建對象使用 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)閉。

contextlib 模塊

除了在類中定義 __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 語句之后。

小結(jié)

  • 上下文管理器是支持上下文管理協(xié)議的對象,也就是實(shí)現(xiàn)了 __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)行處理。

參考資料

上一篇:讀寫文本文件下一篇:HTTP 服務(wù)