什么是上下文管理器
上下文管理器顧名思義是管理上下文的,也就是負責沖鋒和墊后,而讓主人專心完成自己的事情秩命。我們在編寫程序的時候袄友,通常會將一系列操作放到一個語句塊中殿托,當某一條件為真時執(zhí)行該語句快碌尔。有時候,我們需要再執(zhí)行一個語句塊時保持某種狀態(tài)叹坦,并且在離開語句塊后結(jié)束這種狀態(tài)。例如對文件的操作莹捡,我們在打開一個文件進行讀寫操作時需要保持文件處于打開狀態(tài),而等操作完成之后要將文件關(guān)閉。所以寥茫,上下文管理器的任務(wù)是:代碼塊執(zhí)行前準備,代碼塊執(zhí)行后收拾膝迎。上下文管理器是在Python2.5加入的功能,它能夠讓你的代碼可讀性更強并且錯誤更少费尽。
需求的產(chǎn)生
在正常的管理各種系統(tǒng)資源(文件、鎖定和連接)柏卤,在涉及到異常時通常是個棘手的問題敌蚜。異常很可能導(dǎo)致控制流跳過負責釋放關(guān)鍵資源的語句齐媒。例如打開一個文件進行操作時,如果意外情況發(fā)生(磁盤已滿、特殊的終端信號讓其終止等),就會拋出異常,這樣可能最后的文件關(guān)閉操作就不會執(zhí)行。如果這樣的問題頻繁出現(xiàn)眯分,則可能耗盡系統(tǒng)資源。
是的,這樣的問題并不是不可避免。在沒有接觸到上下文管理器之前净响,我們可以用“try/finally”
語句來解決這樣的問題掸掸〔淝铮或許在有些人看來扰付,“try/finally”語句顯得有些繁瑣仁讨。上下文管理器就是被設(shè)計用來簡化“try/finally”語句的,這樣可以讓程序更加簡潔盐固。
With語句
With語句用于執(zhí)行上下文操作,它也是復(fù)合語句的一種,其基本語法如下所示:
with context_expr [as var]:
with_suite
With 語句僅能工作于支持上下文管理協(xié)議(context management protocol)的對象渔隶。也就是說只有內(nèi)建了"上下文管理"的對象才能和 with 一起工作。Python內(nèi)置了一些支持該協(xié)議的對象婉弹,如下所列是一個簡短列表:
- file
- decimal.Context
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore
由以上列表可以看出睬魂,file 是已經(jīng)內(nèi)置了對上下文管理協(xié)議的支持。所以我們可以用下邊的方法來操作文件:
with open('/etc/passwd', 'r') as f:
for eachLine in f:
# ...do stuff with eachLine or f...
上邊的代碼試圖打開一個文件,如果一切正常,把文件對象賦值給 f商佛。然后用迭代器遍歷文件中的每一行,當 完成時,關(guān)閉文件喉钢。無論是在這一段代碼的開始,中間,還是結(jié)束時發(fā)生異常,會執(zhí)行清理的代碼,此 外文件仍會被自動的關(guān)閉。
自定義上下文管理器
要實現(xiàn)上下文管理器良姆,必須實現(xiàn)兩個方法:一個負責進入語句塊的準備操作肠虽,另一個負責離開語句塊的善后操作。Python類包含兩個特殊的方法玛追,分別名為:__enter__
和 __exit__
税课。
- enter: 該方法進入運行時上下文環(huán)境,并返回自身或另一個與運行時上下文相關(guān)的對象痊剖。返回值會賦給 as 從句后面的變量韩玩,as 從句是可選的。
- exit: 該方法退出當前運行時上下文并返回一個布爾值陆馁,該布爾值標明了“如果 with_suit 的退出是由異常引發(fā)的找颓,該異常是否須要被忽略”。如果 exit() 的返回值等于 False叮贩,那么這個異常將被重新引發(fā)一次击狮;如果 exit() 的返回值等于 True,那么這個異常就被無視掉益老,繼續(xù)執(zhí)行后面的代碼彪蓬。
With 語句的實際執(zhí)行流程是這樣的:
1. 執(zhí)行 context_exp 以獲取上下文管理器
2. 加載上下文管理器的 exit() 方法以備稍后調(diào)用
3. 調(diào)用上下文管理器的 enter() 方法
4. 如果有 as var 從句,則將 enter() 方法的返回值賦給 var
5. 執(zhí)行子代碼塊 with_suit
6. 調(diào)用上下文管理器的 exit() 方法捺萌,如果 with_suit 的退出是由異常引發(fā)的寞焙,那么該異常的 type、value 和 traceback 會作為參數(shù)傳給 exit()互婿,否則傳三個 None
7. 如果 with_suit 的退出由異常引發(fā),并且 exit() 的返回值等于 False辽狈,那么這個異常將被重新引發(fā)一次慈参;如果 exit() 的返回值等于 True,那么這個異常就被無視掉刮萌,繼續(xù)執(zhí)行后面的代碼
下面我們自己來實現(xiàn)一個支持上下文管理協(xié)議的文件操作:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# *************************************************************
# Filename @ contextfile.py
# Author @ Huoty
# Create date @ 2015-08-08 17:02:13
# Description @
# *************************************************************
filename = 'my_file.txt'
mode = 'w' # Mode that allows to write to the file
writer = open(filename, mode)
class PypixOpen(object):
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.openedFile = open(self.filename, self.mode)
return self.openedFile
def __exit__(self, *unused):
self.openedFile.close()
# Script starts from here
with PypixOpen(filename, mode) as writer:
writer.write("Hello World from our new Context Manager!")
更加優(yōu)雅的上下文管理(contextlib模塊)
contextlib
模塊提供更易用的上下文管理器驮配。
contextlib.closing
contextlib.closing
方法在語句塊結(jié)束后調(diào)用對象的 close 方法。
from contextlib import closing
import urllib
with closing(urllib.urlopen('http://www.python.org')) as page:
for line in page:
print line
contextlib.nested
contextlib.nested
方法用于替換嵌套的 with 語句。例如壮锻,有兩個文件琐旁,一個讀一個寫,即進行拷貝猜绣。以下是不提倡的用法:
with open('toReadFile', 'r') as reader:
with open('toWriteFile', 'w') as writer:
writer.writer(reader.read())
這里可以用 contextlib.nested 進行優(yōu)化:
with contextlib.nested(open('fileToRead.txt', 'r'), \
open('fileToWrite.txt', 'w')) as (reader, writer):
writer.write(reader.read())
contextlib.contextmanager
contextlib.contextmanager
是一個裝飾器灰殴,它可以用來裝飾被 yield 語句分割成兩部分的函數(shù),以此進行上下文管理掰邢。任何在yield之前的內(nèi)容都可以看做在代碼塊執(zhí)行前的操作牺陶,而任何yield之后的操作都可以看做是代碼塊結(jié)束后要做的操作。如果希望在上下文管理器中使用 “as” 關(guān)鍵字辣之,那么就用 yield 返回你需要的值掰伸,它將通過 as 關(guān)鍵字賦值給新的變量。
from contextlib import contextmanager
@contextmanager
def tag(name):
print "<%s>" % name
yield
print "</%s>" % name
使用 contextlib.contextmanager 時怀估,可以大致套用如下的框架:
from contextlib import contextmanager
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()