with
初識with
在Python中久又,讀寫文件這樣的資源要特別注意巫延,必須在使用完畢后正確關(guān)閉它們。正確關(guān)閉文件資源的一個方法是使用try...finally
:
try:
f = open("/directory/filename", 'r')
f.read()
finally:
# 如果f不存在, 則應(yīng)該是文件對象未打開
if f:
f.close()
但是寫try:...finally:...
非常繁瑣籽孙。Python的with
語句允許我們非常方便地使用資源烈评,而不必?fù)?dān)心資源沒有關(guān)閉火俄,所以上面的代碼可以簡化為:
# 注意f只會在with語句塊有效
with open("/directory/filename", 'r') as f:
f.read()
with的工作原理
并不是只有open()
函數(shù)返回的fp對象才能使用with
語句犯建。實(shí)際上,任何對象瓜客,只要正確實(shí)現(xiàn)了上下文管理适瓦,就可以用于with
語句竿开。
實(shí)現(xiàn)上下文管理是通過__enter__
和__exit__
這兩個方法實(shí)現(xiàn)的. 我們可以簡單通過一個例子來說明with
的內(nèi)部調(diào)用方法
class Generator(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print("There's no way out")
return self
def __exit__(self, exc_type, exc_value, traceback):
# 檢查退出的類型
# 當(dāng)有異常發(fā)生時 exc_type存在值
if exc_type:
print('Open the door')
else:
print('100%')
def generate(self):
print('%s can uses love to generate electricity' % self.name)
我們可以通過以下代碼調(diào)用
# 緊跟with后面的語句被求值后,返回對象的 `__enter__() `方法被調(diào)用,
# 這個方法的返回值將被賦值給as后面的變量
with Generator("LEX") as lex:
lex.generate()
# 當(dāng)with后面的代碼塊全部被執(zhí)行完之后玻熙,將調(diào)用返回對象的 `__exit__()`方法否彩。
其實(shí) with
就是這么簡單, 大部分初學(xué)者只要理解到這里就可以了, 所以小白勿入, 高能級別
上下文管理器
我知道有很多小白還是讀了, 那我先介紹幾個名詞讓你們知男而退
-
上下文管理協(xié)議(Context Management Protocol):包含方法
__enter__
和__exit__
,支持
該協(xié)議的對象要實(shí)現(xiàn)這兩個方法嗦随。
- 上下文管理器(Context Manager):支持上下文管理協(xié)議的對象列荔,這種對象實(shí)現(xiàn)了
__enter__
和__exit__
方法。上下文管理器定義執(zhí)行 with 語句時要建立的運(yùn)行時上下文枚尼,
負(fù)責(zé)執(zhí)行 with 語句塊上下文中的進(jìn)入與退出操作贴浙。通常使用 with 語句調(diào)用上下文管理器,
也可以通過直接調(diào)用其方法來使用署恍。
-
運(yùn)行時上下文(runtime context):由上下文管理器創(chuàng)建崎溃,通過上下文管理器的
__enter__
和
__exit__
方法實(shí)現(xiàn),__enter__
方法在語句體執(zhí)行之前進(jìn)入運(yùn)行時上下文盯质,__exit__
在
語句體執(zhí)行完后從運(yùn)行時上下文退出袁串。with
語句支持運(yùn)行時上下文這一概念。
- 上下文表達(dá)式(Context Expression):with 語句中跟在關(guān)鍵字 with 之后的表達(dá)式呼巷,該表達(dá)式
要返回一個上下文管理器對象囱修。如 例子with
后面的Generator("LEX") as lex:
- 語句體(with-body):with 語句包裹起來的代碼塊,在執(zhí)行語句體之前會調(diào)用上下文管
理器的__enter__
方法王悍,執(zhí)行完語句體之后會執(zhí)行 __exit__
方法蔚袍。
其實(shí)這些術(shù)語不需要你們立刻理解, 只需要在以后的課程或者項(xiàng)目中慢慢體會.
@contextmanager
編寫__enter__
和__exit__
仍然很繁瑣,因此Python的標(biāo)準(zhǔn)庫contextlib
提供了更簡單的寫法, 讓我們更加簡潔的定制自己的上下文管理器. 同樣的愛之代碼走起:
from context import contextmanager
class Generator(object):
def __init__(self, name):
self.name = name
def generate(self):
print('%s can uses love to generate electricity' % self.name)
@contextamnager
def create_generator(name):
print("I'm generating electricity from love again")
lex = Generator(name)
# 返回給with...as...語句
yield lex
print('100%')
@contextmanager
這個decorator必須接受一個 ==generator== 配名,用yield
語句把with ... as var
把變量輸出出去, 然后屬于你的上下文管理器就弄了, 是不是出奇的簡單
使用一下這個管理器, 說說感覺
with create_generator("LEX") as lex:
lex.generate()
@closing
但是少年郎們還沒結(jié)束呢, 讓我們了解一下closing
的作用, 它的作用就是讓不是上下文管理器的對象轉(zhuǎn)化為上下文管理器, 即可使用with
語句
urllib.request import urlopen
with closing(urlopen('https://www.bilibili.com')) as page:
for line in page:
print(line)
其實(shí)closing
內(nèi)部非常好實(shí)現(xiàn), 內(nèi)部還是調(diào)用了contextmanager
方法
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
closing 不同于 @contextmanager, 他的本身不需要生成器, 可謂加上這行代碼, 秒變真男人
總結(jié)
with
關(guān)聯(lián)著對象的__exit__
和__enter__
方法, 你必須清楚他們是何時被調(diào)用, 他們的返回值又返回到哪里去了@contextmanager
所裝飾的對象必須是帶yield
的生成器,yield
會把后面的值返回給with...as...
語句@closing
原先必許在所裝飾的對象定義一個close
方法, 否則會報(bào)錯, 但是現(xiàn)在不會, 所以這種一行真男人得靈藥不多了上下文管理器將會貫穿大型框架結(jié)構(gòu), 如
flask
的 Application上下文, 請求(request)上下文等等, 學(xué)習(xí)上下文管理器可以幫助我們更好理解那些框架的工作原理.
未完待續(xù)...