python 上下文管理器

上下文管理器最常用的是確保正確關閉文件,

with open('/path/to/file', 'r') as f:
    f.read()

with 語句的基本語法,

with expression [as variable]:
    with-block

expression是一個上下文管理器,其實現了enterexit兩個函數。當我們調用一個with語句時, 依次執行一下步驟,
1.首先生成一個上下文管理器expression, 比如open('xx.txt').
2.執行expression.enter()。如果指定了[as variable]說明符,將enter()的返回值賦給variable.
3.執行with-block語句塊.
4.執行expression.exit(),在exit()函數中可以進行資源清理工作.
with語句不僅可以管理文件,還可以管理鎖、連接等等,如,

#管理鎖
import  threading
lock = threading.lock()
with lock:
    #執行一些操作
    pass

#數據庫連接管理
def test_write():
    sql = """      #具體的sql語句
    """
    con = DBConnection()
    with con as cursor:   
        cursor.execute(sql)
        cursor.execute(sql)
        cursor.execute(sql)
自定義上下文管理器

上下文管理器就是實現了上下文協議的類 ,也就是要實現 __enter__()__exit__()兩個方法。

__enter__():主要執行一些環境準備工作,同時返回一資源對象。如果上下文管理器open("test.txt")的enter()函數返回一個文件對象。
__exit__():完整形式為exit(type, value, traceback),這三個參數和調用sys.exec_info()函數返回值是一樣的,分別為異常類型、異常信息和堆棧。如果執行體語句沒有引發異常,則這三個參數均被設為None。否則,它們將包含上下文的異常信息。_exit()方法返回True或False,分別指示被引發的異常有沒有被處理,如果返回False,引發的異常將會被傳遞出上下文。如果exit()函數內部引發了異常,則會覆蓋掉執行體的中引發的異常。處理異常時,不需要重新拋出異常,只需要返回False,with語句會檢測exit()返回False來處理異常。

class test_query:
    def __init__(self):
        pass

    def query(self):
        print('query')

    def __enter__(self):
        # 如果是數據庫連接就可以返回cursor對象
        # cursor = self.cursor
        # return cursor
        print('begin query,')
        return self #由于這里沒有資源對象就返回對象
        
    def __exit__(self,exc_type,exc_value,traceback):
        if traceback is None:
            print('End')
        else:
            print('Error')

        # 如果是數據庫連接提交操作
        # if traceback is None:
        #   self.commit()
        # else:
        #   self.rollback()
        
if __name__ == '__main__':
    with test_query() as q:
        q.query()

contextlib

編寫enterexit仍然很繁瑣,因此Python的標準庫contextlib提供了更簡單的寫法,
基本語法

   @contextmanager
        def some_generator(<arguments>):
            <setup>
            try:
                yield <value>
            finally:
                <cleanup>

生成器函數some_generator就和我們普通的函數一樣,它的原理如下:
some_generator函數在在yield之前的代碼等同于上下文管理器中的enter函數.
yield的返回值等同于enter函數的返回值,即如果with語句聲明了as <variable>,則yield的值會賦給variable.
然后執行<cleanup>代碼塊,等同于上下文管理器的exit函數。此時發生的任何異常都會再次通過yield函數返回。
例,
自動加括號

import contextlib

@contextlib.contextmanager
def tag(name):
    print('<{}>'.format(name))
    yield
    print('</{}>'.format(name))


if __name__ == '__main__':
    with tag('h1') as t:
        print('hello')
        print('context')

管理鎖

@contextmanager
def locked(lock):
    lock.acquire()
    try:
        yield
    finally:
        lock.release()

with locked(myLock):
    #代碼執行到這里時,myLock已經自動上鎖
    pass
    #執行完后會,會自動釋放鎖

管理文件關閉

@contextmanager
def myopen(filename, mode="r"):
    f = open(filename,mode)
    try:
        yield f
    finally:
        f.close()

with myopen("test.txt") as f:
    for line in f:
        print(line)

管理數據庫回滾

@contextmanager
def transaction(db):
    db.begin()
    try:
        yield 
    except:
        db.rollback()
        raise
    else:
        db.commit()

with transaction(mydb):
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)
    mydb.cursor.execute(sql)

這就很方便!
如果一個對象沒有實現上下文,我們就不能把它用于with語句。這個時候,可以用closing()來把該對象變為上下文對象。它的exit函數僅僅調用傳入參數的close函數.
例如,用with語句使用urlopen():

import contextlib
from urllib.request import urlopen

if __name__ == '__main__':
    with contextlib.closing(urlopen('https://www.python.org')) as page:
        for line in page:
            print(line)

closing也是一個經過@contextmanager裝飾的generator,這個generator編寫起來其實非常簡單:

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

嵌套使用

import contextlib
from urllib.request import urlopen

if __name__ == '__main__':
    with contextlib.closing(urlopen('https://www.python.org')) as page,\
            contextlib.closing(urlopen('https://www.python.org')) as p:
        for line in page:
            print(line)
                print(p)

在2.x中需要使用contextlib.nested()才能使用嵌套,3.x中可以直接使用。
更多函數可以參考官方文檔https://docs.python.org/3/library/contextlib.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容