談一談Python的上下文管理器 – 思誠之道

regedit2100 7年前發布 | 11K 次閱讀 Python Python開發

經常在Python代碼中看到with語句,仔細分析下,會發現這個with語句功能好強,可以自動關閉資源。這個在Python中叫上下文管理器Context Manager。那我們要怎么用它,什么時候用它呢。這里我們就來聊一聊。

上下文管理器的作用

很多情況,當我們使用完一個資源后,我們需要手動的關閉掉它,比如操作文件,建立數據庫連接等。但是,在使用資源的過程中,如果遇到異常,很可能錯誤被直接拋出,導致來不及關閉資源。所以在大部分程序語言里,我們使用”try-finally”語句來確保資源會關閉。比如下面的Python寫文件代碼:

try:
    f = open('test.txt', 'a+')
    f.write('Foo\n')
finally:
    f.close()

這樣做固然沒有問題,但是當”try-finally”中間的邏輯復雜,而且還帶有各種嵌套的話,代碼就很不容易維護。Python的with語句,可以說功能同上面的”try-finally”幾乎一樣,但代碼看上去簡潔的多,我們來實現同樣的功能:

with open('test.txt', 'a+') as f:
    f.write('Foo\n')

with語句后面跟著open()方法,如果它有返回值的話,可以使用as語句將其賦值給f。在with語句塊退出時,”f.close()”方法會自動被調用,即使”f.write()”出現異常,也能確保close()方法被調用。

自定義類來使用上下文管理器

上例中”open()”方法是Python自帶的,那我們怎么定義自己的類型來使用with語句呢。其實只要你的類定義了”__enter__()”和”__exit__()”方法,就可以使用Python的上下文管理器了。”__enter__()”方法會在with語句進入時被調用,其返回值會賦給as關鍵字后的變量;而”__exit__()”方法會在with語句塊退出后自動被調用。

我們來實現個跟上節一樣的文件寫入功能:

class OpenFileDemo(object):
    def __init__(self, filename):
        self.filename = filename

    def __enter__(self):
        self.f = open(self.filename, 'a+')
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

with OpenFileDemo('test.txt') as f:
    f.write('Foo\n')

異常處理

肯定有朋友注意到上面的”__exit__()”帶了三個參數,是的,他們是用來異常處理的。大部分情況下,我們希望with語句中遇到的異常最后被拋出,但也有時候,我們想處理這些異常。”__exit__()”方法中的三個參數exc_type, exc_val, exc_tb分別代表異常類型,異常值,和異常的Traceback。當你處理完異常后,你可以讓”__exit__()”方法返回True,此時該異常就會不會再被拋出。比如我們將上例中的”__exit__()”方法改一下:

def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        if exc_type != SyntaxError:
            return True
        return False  # Only raise exception when SyntaxError

現在,如果遇到SyntaxError的話,異常會被正常拋出,而其他異常的話都會被忽略。

contextlib模塊

Python中還有一個contextlib模塊提供一些簡便的上下文管理器功能。

closing()方法

如果說with語句塊在退出時會自動調用”__exit__()”方法的話,那用了”contextlib.closing()”的with語句塊則在退出時會自動調用”close()”方法。看一下示例:

import contextlib

class Resource(object):
    def open(self):
        print 'Open Resource'

    def close(self):
        print 'Close Resource'

with contextlib.closing(Resource()) as r:
    r.open()

程序運行后,會打印出

OpenResource
CloseResource

說明Resource類創建的對象被賦給了as關鍵字后面的變量r,而with語句塊退出時,自動調用了”r.close()”方法。

contextmanager裝飾器

“@contextlib.contextmanager”是一個裝飾器,由它修飾的方法會有兩部分構成,中間由yield關鍵字分開。由此方法創建的上下文管理器,在代碼塊執行前會先執行yield上面的語句;在代碼塊執行后會再執行yield下面的語句。看個例子比較容易明白:

import contextlib
import time

@contextlib.contextmanager
def timeit():
    start = time.time()
    yield
    end = time.time()
    usedTime = (end - start) * 1000
    print 'Use time %d ms' % usedTime

with timeit():
    time.sleep(1)

這個”timeit()”方法實現了一個計時器,它會計算由他生成的with語句塊執行時間。可以看出,yield上面的語句就如同之間介紹過的”__enter__()”方法,而yield下面的語句就如同”__exit__()”方法。而yield部分就是with語句塊中的代碼。如果yield后面帶參數的話,我們就可以用as關鍵字賦值給后面的變量,比如上例:

@contextlib.contextmanager
def timeit():
    start = time.time()
    yield start
    #...

with timeit() as starttime:
    print starttime
    #...

需要注意的是,”@contextlib.contextmanager”不像之前介紹的”__exit__()”方法,遇到異常也會執行。也就是with語句塊拋出異常的話,yield后面的代碼將不會被執行。所以,必要時你需要對yield語句使用”try-finally”。

 

來自:http://python.jobbole.com/87135/

 

 本文由用戶 regedit2100 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!