python with

術(shù)語

要使用 with 語句,首先要明白上下文管理器這一概念嫩絮。有了上下文管理器埋涧,with 語句才能工作。
下面是一組與上下文管理器和with 語句有關(guān)的概念虫蝶。
上下文管理協(xié)議(Context Management Protocol):包含方法 __enter__()__exit__() ,支持
該協(xié)議的對象要實現(xiàn)這兩個方法倦西。
上下文管理器(Context Manager):支持上下文管理協(xié)議的對象能真,這種對象實現(xiàn)了
__enter__()__exit__() 方法。上下文管理器定義執(zhí)行 with 語句時要建立的運行時上下文扰柠,
負責(zé)執(zhí)行 with 語句塊上下文中的進入與退出操作粉铐。通常使用 with 語句調(diào)用上下文管理器,
也可以通過直接調(diào)用其方法來使用卤档。
運行時上下文(runtime context):由上下文管理器創(chuàng)建蝙泼,通過上下文管理器的 __enter__()
__exit__() 方法實現(xiàn),__enter__() 方法在語句體執(zhí)行之前進入運行時上下文劝枣,__exit__()
語句體執(zhí)行完后從運行時上下文退出汤踏。with 語句支持運行時上下文這一概念。
上下文表達式(Context Expression):with 語句中跟在關(guān)鍵字 with 之后的表達式舔腾,該表達式
要返回一個上下文管理器對象溪胶。
語句體(with-body):with 語句包裹起來的代碼塊,在執(zhí)行語句體之前會調(diào)用上下文管理器的__enter__() 方法琢唾,執(zhí)行完語句體之后會執(zhí)行__exit__() 方法载荔。


1.基本語法和工作原理
with context_expression [as target(s)]:
        with-body
with open(r'somefileName') as somefile:
        for line in somefile:
            print line
            # ...more code

等價于傳統(tǒng)方式, try/finally 方式操作文件對象

 somefile = open(r'somefileName')
    try:
        for line in somefile:
            print line
            # ...more code
    finally:
        somefile.close()

with 語句的執(zhí)行過程類似如下代碼塊:

    context_manager = context_expression
    exit = type(context_manager).__exit__  
    value = type(context_manager).__enter__(context_manager)
    exc = True   # True 表示正常執(zhí)行,即便有異常也忽略采桃;False 表示重新拋出異常懒熙,需要對異常進行處理
    try:
        try:
            target = value  # 如果使用了 as 子句
            with-body     # 執(zhí)行 with-body
        except:
            # 執(zhí)行過程中有異常發(fā)生
            exc = False
            # 如果 __exit__ 返回 True丘损,則異常被忽略;如果返回 False工扎,則重新拋出異常
            # 由外層代碼對異常進行處理
            if not exit(context_manager, *sys.exc_info()):
                raise
    finally:
        # 正常退出徘钥,或者通過 statement-body 中的 break/continue/return 語句退出
        # 或者忽略異常退出
        if exc:
            exit(context_manager, None, None, None) 
        # 缺省返回 None,None 在布爾上下文中看做是 False

1.執(zhí)行 context_expression肢娘,生成上下文管理器 context_manager
2.調(diào)用上下文管理器的__enter__() 方法呈础;如果使用了 as 子句,則將__enter__() 方法的返回值賦值給 as 子句中的 target(s)
3.執(zhí)行語句體 with-body
4.不管是否執(zhí)行過程中是否發(fā)生了異常橱健,執(zhí)行上下文管理器的 __exit__() 方法而钞,__exit__() 方法負責(zé)執(zhí)行“清理”工作,如釋放資源等拘荡。如果執(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)
5.出現(xiàn)異常時,如果 __exit__(type, value, traceback) 返回 False蟋定,則會重新拋出異常粉臊,讓with 之外的語句邏輯來處理異常,這也是通用做法驶兜;如果返回 True扼仲,則忽略異常,不再對異常進行處理


2.自定義上下文管理器

自定義的上下文管理器要實現(xiàn)上下文管理協(xié)議所需要的 __enter__()__exit__() 兩個方法
(1).context_manager.__enter__():進入上下文管理器的運行時上下文促王,在語句體執(zhí)行前調(diào)用犀盟。with 語句將該方法的返回值賦值給 as 子句中的 target,如果指定了 as 子句的話
(2).context_manager.__exit__(exc_type, exc_value, exc_traceback) :退出與上下文管理器相關(guān)的運行時上下文蝇狼,返回一個布爾值表示是否對發(fā)生的異常進行處理。參數(shù)表示引起退出操作的異常倡怎,如果退出時沒有發(fā)生異常迅耘,則3個參數(shù)都為None。如果發(fā)生異常监署,返回True 表示不處理異常颤专,否則會在退出該方法后重新拋出異常以由 with 語句之外的代碼邏輯進行處理。如果該方法內(nèi)部產(chǎn)生異常钠乏,則會取代由 statement-body 中語句產(chǎn)生的異常栖秕。要處理異常時,不要顯示重新拋出異常晓避,即不能重新拋出通過參數(shù)傳遞進來的異常簇捍,只需要將返回值設(shè)置為 False 就可以了只壳。之后,上下文管理代碼會檢測是否__exit__()失敗來處理異常

假設(shè)有一個資源 DummyResource暑塑,這種資源需要在訪問前先分配吼句,使用完后再釋放掉;分配操作可以放到 enter() 方法中事格,釋放操作可以放到 exit() 方法中惕艳。簡單起見,這里只通過打印語句來表明當(dāng)前的操作驹愚,并沒有實際的資源分配與釋放远搪。
自定義支持 with 語句的對象

 class DummyResource:
    def __init__(self, tag):
            self.tag = tag
            print 'Resource [%s]' % tag
        def __enter__(self):
            print '[Enter %s]: Allocate resource.' % self.tag
            return self   # 可以返回不同的對象
        def __exit__(self, exc_type, exc_value, exc_tb):
            print '[Exit %s]: Free resource.' % self.tag
            if exc_tb is None:
                print '[Exit %s]: Exited without exception.' % self.tag
            else:
                print '[Exit %s]: Exited with exception raised.' % self.tag
                return False   # 可以省略逢捺,缺省的None也是被看做是False

使用自定義的支持 with 語句的對象

  with DummyResource('Normal'):
        print '[with-body] Run without exceptions.'

    with DummyResource('With-Exception'):
        print '[with-body] Run with exception.'
        raise Exception
        print '[with-body] Run with exception. Failed to finish statement-body!'

執(zhí)行結(jié)果

 Resource [Normal]
    [Enter Normal]: Allocate resource.
    [with-body] Run without exceptions.
    [Exit Normal]: Free resource.
    [Exit Normal]: Exited without exception.

Resource [With-Exception]
    [Enter With-Exception]: Allocate resource.
    [with-body] Run with exception.
    [Exit With-Exception]: Free resource.
    [Exit With-Exception]: Exited with exception raised.

    Traceback (most recent call last):
      File "G:/demo", line 20, in <module>
       raise Exception
    Exception

3.contextlib 模塊

contextlib 模塊提供了3個對象:裝飾器 contextmanager、函數(shù) nested 和上下文管理器 closing蒸甜。使用這些對象,可以對已有的生成器函數(shù)或者對象進行包裝柠新,加入對上下文管理協(xié)議的支持窍荧,避免了專門編寫上下文管理器來支持 with 語句。
裝飾器 contextmanager
contextmanager 用于對生成器函數(shù)進行裝飾恨憎,生成器函數(shù)被裝飾以后蕊退,返回的是一個上下文管理器,其 __enter__()__exit__()方法由 contextmanager 負責(zé)提供憔恳,而不再是之前的迭代子瓤荔。被裝飾的生成器函數(shù)只能產(chǎn)生一個值,否則會導(dǎo)致異常 RuntimeError钥组;產(chǎn)生的值會賦值給 as 子句中的 target输硝,如果使用了 as 子句的話。下面看一個簡單的例子程梦。
裝飾器 contextmanager 使用示例

 from contextlib import contextmanager

    @contextmanager
    def demo():
        print '[Allocate resources]'
        print 'Code before yield-statement executes in __enter__'
        yield '*** contextmanager demo ***'
        print 'Code after yield-statement executes in __exit__'
        print '[Free resources]'

    with demo() as value:
        print 'Assigned Value: %s' % value

** contextmanager 使用示例執(zhí)行結(jié)果**

 [Allocate resources]
    Code before yield-statement executes in __enter__
    Assigned Value: *** contextmanager demo ***
    Code after yield-statement executes in __exit__
    [Free resources]

可以看到点把,生成器函數(shù)中 yield 之前的語句在__enter__()方法中執(zhí)行,yield 之后的語句在__exit__()中執(zhí)行屿附,而 yield 產(chǎn)生的值賦給了 as 子句中的 value 變量郎逃。
需要注意的是,contextmanager 只是省略了__enter__() / __exit__()的編寫挺份,但并不負責(zé)實現(xiàn)資源的“獲取”和“清理”工作褒翰;“獲取”操作需要定義在 yield 語句之前,“清理”操作需要定義 yield 語句之后,這樣 with 語句在執(zhí)行 __enter__() / __exit__()方法時會執(zhí)行這些語句以獲取/釋放資源优训,即生成器函數(shù)中需要實現(xiàn)必要的邏輯控制朵你,包括資源訪問出現(xiàn)錯誤時拋出適當(dāng)?shù)漠惓!?br> 函數(shù) nested
nested 可以將多個上下文管理器組織在一起型宙,避免使用嵌套 with 語句撬呢。

 with nested(A(), B(), C()) as (X, Y, Z):
         # with-body code here

nested 執(zhí)行過程

 with A() as X:
        with B() as Y:
            with C() as Z:
                 # with-body code here

需要注意的是,發(fā)生異常后妆兑,如果某個上下文管理器的 exit() 方法對異常處理返回 False魂拦,則更外層的上下文管理器不會監(jiān)測到異常。
上下文管理器 closing
上下文管理 closing 實現(xiàn)

  class closing(object):
        # help doc here
        def __init__(self, thing):
            self.thing = thing
        def __enter__(self):
            return self.thing
        def __exit__(self, *exc_info):
            self.thing.close()

上下文管理器會將包裝的對象賦值給 as 子句的 target 變量搁嗓,同時保證打開的對象在 with-body 執(zhí)行完后會關(guān)閉掉芯勘。closing 上下文管理器包裝起來的對象必須提供 close() 方法的定義,否則執(zhí)行時會報 AttributeError 錯誤腺逛。
自定義支持 closing 的對象

class ClosingDemo(object):
        def __init__(self):
            self.acquire()
        def acquire(self):
            print 'Acquire resources.'
        def free(self):
            print 'Clean up any resources acquired.'
        def close(self):
            self.free()

    with closing(ClosingDemo()):
        print 'Using resources'

** 自定義 closing 對象的輸出結(jié)果**

Acquire resources.
    Using resources
    Clean up any resources acquired.

closing 適用于提供了 close() 實現(xiàn)的對象荷愕,比如網(wǎng)絡(luò)連接、數(shù)據(jù)庫連接等棍矛,也可以在自定義類時通過接口 close() 來執(zhí)行所需要的資源“清理”工作安疗。
[1]https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/#icomments

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市够委,隨后出現(xiàn)的幾起案子荐类,更是在濱河造成了極大的恐慌,老刑警劉巖茁帽,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吊输,居然都是意外死亡季蚂,警方通過查閱死者的電腦和手機脂信,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門狰闪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埋泵,“玉大人,你說我怎么就攤上這事礁蔗⊙闵纾” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵磺浙,是天一觀的道長撕氧。 經(jīng)常有香客問我喇完,道長,這世上最難降的妖魔是什么不脯? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任防楷,我火速辦了婚禮域帐,結(jié)果婚禮上是整,老公的妹妹穿的比我還像新娘。我一直安慰自己龙优,他們只是感情好事秀,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著易迹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪睹欲。 梳的紋絲不亂的頭發(fā)上一屋,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天冀墨,我揣著相機與錄音诽嘉,去河邊找鬼弟翘。 笑死,一個胖子當(dāng)著我的面吹牛岔乔,可吹牛的內(nèi)容都是我干的滚躯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼茁影,長吁一口氣:“原來是場噩夢啊……” “哼募闲!你這毒婦竟也來了愿待?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤要出,失蹤者是張志新(化名)和其女友劉穎患蹂,沒想到半個月后砸紊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡沼溜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年盛末,在試婚紗的時候發(fā)現(xiàn)自己被綠了否淤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡檐嚣,死狀恐怖啰扛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞍帝,我是刑警寧澤煞茫,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布续徽,位于F島的核電站,受9級特大地震影響钦扭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜其弊,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一膀斋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧籽御,春花似錦惰匙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悯仙。三九已至锡垄,卻和暖如春祭隔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疾渴。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工搞坝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敦第。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓申尼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垫桂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容