本文環(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)題
- 在
__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__
,代碼就終止了
-
__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)
-
__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é)
-
__enter__
需要返回一個(gè)對(duì)象盖桥,才能通過(guò)with...as
把一個(gè)變量引用到這個(gè)對(duì)象上 -
__exit__
有4個(gè)參數(shù)灾螃,后面三個(gè)分別是exc_type
,exc_value
和traceback
-
__enter__
必須正確返回后(就算是返回默認(rèn)的None
),上下文管理器協(xié)議才生效 -
__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