【Python進(jìn)階】上下文管理器和with語(yǔ)句

本文環(huán)境

win7_64 + Python 2.7.10

文件操作

一般文件操作可以通過(guò)open的方式獲取一個(gè)文件對(duì)象fp,比如:

fp = open('test.txt', 'r')

然后我們可以通過(guò)fp對(duì)文件進(jìn)行讀操作,操作完成后,我們需要通過(guò)調(diào)用fp對(duì)象的close方法顯示的關(guān)閉資源,以便讓python可以正確的釋放相關(guān)系統(tǒng)資源

fp.close()

如果在close之前出現(xiàn)了異常,導(dǎo)致代碼不能正常走到close,就會(huì)出現(xiàn)資源不能正常釋放的情況

fp = open('test.txt', 'r')
a = 1 / 0 # 這里引發(fā)了異常,下面的代碼無(wú)法執(zhí)行
fp.close()

一般可以通過(guò)try...except...finally來(lái)捕獲異常,因?yàn)閒inally里面的代碼是一定會(huì)走到,故可以把資源釋放的動(dòng)作放到這里

fp = open('test.txt', 'r')
try:
    do_something()
    a = 1/ 0
except:
    do_exc()
finally:
    fp.close() # 能正確釋放

上下文管理器

上面的代碼雖然可以滿足我們的要求,但是比較冗長(zhǎng),而且也不符合python提倡的代碼簡(jiǎn)潔性,所以有一個(gè)概念就是上下文管理器,類比到代碼就是with語(yǔ)句

with open('test.txt', 'r') as fp:
    do_something()

上面的代碼,就可以自動(dòng)的在執(zhí)行完do_something()這段邏輯后正確的進(jìn)行釋放,會(huì)自動(dòng)的調(diào)用close

實(shí)現(xiàn)自己的上下文管理器

可以通過(guò)定義一個(gè)類,滿足上下文管理器協(xié)議的要求定義,就可以用with語(yǔ)句來(lái)調(diào)用,類里面需要定義__enter____exit__兩個(gè)方法即可

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
def test():
    print('test')
        
with MyContext():
    test()

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

__init__
__enter__
test
__exit__

執(zhí)行順序很明顯
假如我們?cè)?code>test里面出現(xiàn)了異常,在看看會(huì)執(zhí)行哪些方法

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
def test():
    print('test')
    return 1 / 0 #這里引發(fā)異常
        
with MyContext():
    test()

結(jié)果

__init__
__enter__
test
__exit__
Traceback (most recent call last):
  File "test.py", line 19, in <module>
    test()
  File "test.py", line 16, in test
    return 1 / 0
ZeroDivisionError: integer division or modulo by zero

同樣,所有的都執(zhí)行了,就算test引發(fā)了異常,但是__exit__仍然會(huì)執(zhí)行
上面都是用的with語(yǔ)句,現(xiàn)在來(lái)看看with...as

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
# 這個(gè)with語(yǔ)句不等同于 my = MyContext(),而是等同于my = MyContext().__enter__(),由于__enter__并
# 沒(méi)有返回任何值,默認(rèn)就是None,故在調(diào)用my.fun()會(huì)拋出異常
with MyContext() as my:
    my.func()

結(jié)果

__init__
__enter__
__exit__
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    my.func()
AttributeError: 'NoneType' object has no attribute 'func'

我們把__enter__返回self自己

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
with MyContext() as my:
    my.func()

結(jié)果就可以正常執(zhí)行

__init__
__enter__
func
__exit__

執(zhí)行順序和異常拋出問(wèn)題

  1. __enter__返回之前出現(xiàn)異常,那么上下文管理器是沒(méi)有形成的,此時(shí)__exit__是不能被執(zhí)行的,除非顯示調(diào)用
    首先看__init__里面引發(fā)異常
class MyContext:
    def __init__(self):
        print('__init__')
        1 / 0
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
with MyContext() as my:
    my.func()

結(jié)果只執(zhí)行到了__init__,引發(fā)異常后,代碼終止,連__enter__都無(wú)法執(zhí)行

__init__
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    with MyContext() as my:
  File "test.py", line 4, in __init__
    1 / 0
ZeroDivisionError: integer division or modulo by zero

在來(lái)看看__enter__自己出現(xiàn)異常

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        1 / 0
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        
with MyContext() as my:
    my.func()

結(jié)果是

__init__
__enter__
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    with MyContext() as my:
  File "test.py", line 10, in __enter__
    1 / 0
ZeroDivisionError: integer division or modulo by zero

雖然執(zhí)行到了__enter__,但是仍然沒(méi)有執(zhí)行__exit__,代碼就終止了

  1. __exit__的三個(gè)參數(shù)對(duì)應(yīng)了引發(fā)異常后的三個(gè)參數(shù),對(duì)應(yīng)的就是sys.exc_info()返回的對(duì)象,如果沒(méi)有引發(fā)異常,它們的值都是None
    首先看下出現(xiàn)異常它們的值
class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        1 / 0
        print('func')
        
    def __enter__(self):
        print('__enter__')
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        print(exc_type, exc_value, traceback)
        
with MyContext() as my:
    my.func()

結(jié)果是

__init__
__enter__
__exit__
# 分別是異常類型,異常值和異常跟蹤對(duì)象
(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x000000000269EC08>)
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    my.func()
  File "test.py", line 6, in func
    1 / 0
ZeroDivisionError: integer division or modulo by zero

再來(lái)看看未出現(xiàn)異常的時(shí)候

class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        print('func')
        
    def __enter__(self):
        print('__enter__')
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        print(exc_type, exc_value, traceback)
        
with MyContext() as my:
    my.func()

結(jié)果是

__init__
__enter__
func
__exit__
# 三個(gè)值都是None
(None, None, None)
  1. __exit__默認(rèn)是返回的None,上面的例子可以看出,異常雖然出現(xiàn)了,但是仍然會(huì)繼續(xù)拋出,我們可以通過(guò)讓__exit__返回True來(lái)屏蔽異常
class MyContext:
    def __init__(self):
        print('__init__')
        
    def func(self):
        1 / 0
        print('func')
        
    def __enter__(self):
        print('__enter__')
        return self
        
    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__')
        print(exc_type, exc_value, traceback)
        return True
        
with MyContext() as my:
    my.func()

結(jié)果是

__init__
__enter__
__exit__
(<type 'exceptions.ZeroDivisionError'>, ZeroDivisionError('integer division or modulo by zero',), <traceback object at 0x000000000267EC08>)

由于__exit__返回了True,異常被屏蔽,代碼不會(huì)終止,會(huì)繼續(xù)往下執(zhí)行,但是注意,屏蔽異常,一定要自己在__exit__里面處理異常,或者你根本不care這個(gè)異常

總結(jié)

  1. __enter__需要返回一個(gè)對(duì)象,才能通過(guò)with...as把一個(gè)變量引用到這個(gè)對(duì)象上
  2. __exit__有4個(gè)參數(shù),后面三個(gè)分別是exc_type, exc_valuetraceback
  3. __enter__必須正確返回后(就算是返回默認(rèn)的None),上下文管理器協(xié)議才生效
  4. __exit__默認(rèn)返回None,出現(xiàn)異常仍然會(huì)繼續(xù)拋出,返回True則會(huì)屏蔽異常

參考

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/2/reference/compound_stmts.html#the-with-statement

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容