今天就跟大家聊聊有關Python上下文管理器的本質(zhì)及用法是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
我們提供的服務有:成都做網(wǎng)站、網(wǎng)站建設、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、承德縣ssl等。為數(shù)千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術的承德縣網(wǎng)站制作公司
舉個例子,你在寫Python代碼的時候經(jīng)常將一系列操作放在一個語句塊中:
當某條件為真 – 執(zhí)行這個語句塊
當某條件為真 – 循環(huán)執(zhí)行這個語句塊
有時候我們需要在當程序在語句塊中運行時保持某種狀態(tài),并且在離開語句塊后結束這種狀態(tài)。
所以,事實上上下文管理器的任務是 – 代碼塊執(zhí)行前準備,代碼塊執(zhí)行后收拾。
上下文管理器是在Python2.5加入的功能,它能夠讓你的代碼可讀性更強并且錯誤更少。接下來,讓我們來看看該如何使用。
看代碼是***的學習方式,來看看我們通常是如何打開一個文件并寫入”Hello World”?
filename = 'my_file.txt' mode = 'w' # Mode that allows to write to the file writer = open(filename, mode) writer.write('Hello ') writer.write('World') writer.close()
1-2行,我們指明文件名以及打開方式(寫入)。
第3行,打開文件,4-5行寫入“Hello world”,第6行關閉文件。
這樣不就行了,為什么還需要上下文管理器?但是我們忽略了一個很小但是很重要的細節(jié):如果我們沒有機會到達第6行關閉文件,那會怎樣?
舉個例子,磁盤已滿,因此我們在第4行嘗試寫入文件時就會拋出異常,而第6行則根本沒有機會執(zhí)行。
當然,我們可以使用try-finally語句塊來進行包裝:
writer = open(filename, mode) try: writer.write('Hello ') writer.write('World') finally: writer.close()
finally語句塊中的代碼無論try語句塊中發(fā)生了什么都會執(zhí)行。因此可以保證文件一定會關閉。這么做有什么問題么?當然沒有,但當我們進行一些比寫入“Hello world”更復雜的事情時,try-finally語句就會變得丑陋無比。例如我們要打開兩個文件,一個讀一個寫,兩個文件之間進行拷貝操作,那么通過with語句能夠保證兩者能夠同時被關閉。
OK,讓我們把事情分解一下:
首先,創(chuàng)建一個名為“writer”的文件變量。
然后,對writer執(zhí)行一些操作。
***,關閉writer。
這樣是不是優(yōu)雅多了?
with open(filename, mode) as writer: writer.write('Hello ') writer.write('World')
讓我們深入一點,“with”是一個新關鍵詞,并且總是伴隨著上下文管理器出現(xiàn)?!皁pen(filename, mode)”曾經(jīng)在之前的代碼中出現(xiàn)。“as”是另一個關鍵詞,它指代了從“open”函數(shù)返回的內(nèi)容,并且把它賦值給了一個新的變量。“writer”是一個新的變量名。
2-3行,縮進開啟一個新的代碼塊。在這個代碼塊中,我們能夠對writer做任意操作。這樣我們就使用了“open”上下文管理器,它保證我們的代碼既優(yōu)雅又安全。它出色的完成了try-finally的任務。
open函數(shù)既能夠當做一個簡單的函數(shù)使用,又能夠作為上下文管理器。這是因為open函數(shù)返回了一個文件類型(file type)變量,而這個文件類型實現(xiàn)了我們之前用到的write方法,但是想要作為上下文管理器還必須實現(xiàn)一些特殊的方法,我會在接下來的小節(jié)中介紹。
讓我們來寫一個“open”上下文管理器。
要實現(xiàn)上下文管理器,必須實現(xiàn)兩個方法 – 一個負責進入語句塊的準備操作,另一個負責離開語句塊的善后操作。同時,我們需要兩個參數(shù):文件名和打開方式。
Python類包含兩個特殊的方法,分別名為:__enter__以及__exit__(雙下劃線作為前綴及后綴)。
當一個對象被用作上下文管理器時:
__enter__ 方法將在進入代碼塊前被調(diào)用。
__exit__ 方法則在離開代碼塊之后被調(diào)用(即使在代碼塊中遇到了異常)。
下面是上下文管理器的一個例子,它分別進入和離開代碼塊時進行打印。
class PypixContextManagerDemo: def __enter__(self): print 'Entering the block' def __exit__(self, *unused): print 'Exiting the block' with PypixContextManagerDemo(): print 'In the block' #Output: #Entering the block #In the block #Exiting the block
注意一些東西:
沒有傳遞任何參數(shù)。
在此沒有使用“as”關鍵詞。
稍后我們將討論__exit__方法的參數(shù)設置。
我們?nèi)绾谓o一個類傳遞參數(shù)?其實在任何類中,都可以使用__init__方法,在此我們將重寫它以接收兩個必要參數(shù)(filename, mode)。
當我們進入語句塊時,將會使用open函數(shù),正如***個例子中那樣。而當我們離開語句塊時,將關閉一切在__enter__函數(shù)中打開的東西。
以下是我們的代碼:
class PypixOpen: 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() with PypixOpen(filename, mode) as writer: writer.write("Hello World from our new Context Manager!")
來看看有哪些變化:
3-5行,通過__init__接收了兩個參數(shù)。
7-9行,打開文件并返回。
12行,當離開語句塊時關閉文件。
14-15行,模仿open使用我們自己的上下文管理器。
除此之外,還有一些需要強調(diào)的事情:
我們完全忽視了語句塊內(nèi)部可能出現(xiàn)的問題。
如果語句塊內(nèi)部發(fā)生了異常,__exit__方法將被調(diào)用,而異常將會被重新拋出(re-raised)。當處理文件寫入操作時,大部分時間你肯定不希望隱藏這些異常,所以這是可以的。而對于不希望重新拋出的異常,我們可以讓__exit__方法簡單的返回True來忽略語句塊中發(fā)生的所有異常(大部分情況下這都不是明智之舉)。
我們可以在異常發(fā)生時了解到更多詳細的信息,完備的__exit__函數(shù)簽名應該是這樣的:
def __exit__(self, exc_type, exc_val, exc_tb)
這樣__exit__函數(shù)就能夠拿到關于異常的所有信息(異常類型,異常值以及異常追蹤信息),這些信息將幫助異常處理操作。在這里我將不會詳細討論異常處理該如何寫,以下是一個示例,只負責拋出SyntaxErrors異常。
class RaiseOnlyIfSyntaxError: def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): return SyntaxError != exc_type
contextlib是一個Python模塊,作用是提供更易用的上下文管理器。
假設我們有一個創(chuàng)建數(shù)據(jù)庫函數(shù),它將返回一個數(shù)據(jù)庫對象,并且在使用完之后關閉相關資源(數(shù)據(jù)庫連接會話等)
我們可以像以往那樣處理或是通過上下文管理器:
with contextlib.closing(CreateDatabase()) as database: database.query()
contextlib.closing方法將在語句塊結束后調(diào)用數(shù)據(jù)庫的關閉方法。
另一個很cool的特性能夠有效地幫助我們減少嵌套:
假設我們有兩個文件,一個讀一個寫,需要進行拷貝。
以下是不提倡的:
with open('toReadFile', 'r') as reader: with open('toWriteFile', 'w') as writer: writer.writer(reader.read())
可以通過contextlib.nested進行簡化:
with contextlib.nested(open('fileToRead.txt', 'r'), open('fileToWrite.txt', 'w')) as (reader, writer): writer.write(reader.read())
在Python2.7中這種寫法被一種新語法取代:
with open('fileToRead.txt', 'r') as reader, \ open('fileToWrite.txt', 'w') as writer: writer.write(reader.read())
對于Python高級玩家來說,任何能夠被yield關鍵詞分割成兩部分的函數(shù),都能夠通過裝飾器裝飾的上下文管理器來實現(xiàn)。任何在yield之前的內(nèi)容都可以看做在代碼塊執(zhí)行前的操作,而任何yield之后的操作都可以放在exit函數(shù)中。
這里我舉一個線程鎖的例子:
鎖機制保證兩段代碼在同時執(zhí)行時不會互相干擾。例如我們有兩塊并行執(zhí)行的代碼同時寫一個文件,那我們將得到一個混合兩份輸入的錯誤文件。但如果我們能有一個鎖,任何想要寫文件的代碼都必須首先獲得這個鎖,那么事情就好辦了。如果你想了解更多關于并發(fā)編程的內(nèi)容,請參閱相關文獻。
下面是線程安全寫函數(shù)的例子:
import threading lock = threading.Lock() def safeWriteToFile(openedFile, content): lock.acquire() openedFile.write(content) lock.release()
接下來,讓我們用上下文管理器來實現(xiàn),回想之前關于yield和contextlib的分析:
@contextlib.contextmanager def loudLock(): print 'Locking' lock.acquire() yield print 'Releasing' lock.release() with loudLock(): print 'Lock is locked: %s' % lock.locked() print 'Doing something that needs locking' #Output: #Locking #Lock is locked: True #Doing something that needs locking #Releasing
特別注意,這不是異常安全(exception safe)的寫法。如果你想保證異常安全,請對yield使用try語句。幸運的是threading。lock已經(jīng)是一個上下文管理器了,所以我們只需要簡單地:
@contextlib.contextmanager def loudLock(): print 'Locking' with lock: yield print 'Releasing'
因為threading.lock在異常發(fā)生時會通過__exit__函數(shù)返回False,這將在yield被調(diào)用是被重新拋出。這種情況下鎖將被釋放,但對于“print ‘Releasing’”的調(diào)用則不會被執(zhí)行,除非我們重寫try-finally。
如果你希望在上下文管理器中使用“as”關鍵字,那么就用yield返回你需要的值,它將通過as關鍵字賦值給新的變量。
看完上述內(nèi)容,你們對Python上下文管理器的本質(zhì)及用法是什么有進一步的了解嗎?如果還想了解更多知識或者相關內(nèi)容,請關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。
當前名稱:Python上下文管理器的本質(zhì)及用法是什么
地址分享:http://redsoil1982.com.cn/article10/jhedgo.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供電子商務、移動網(wǎng)站建設、外貿(mào)網(wǎng)站建設、網(wǎng)站導航、網(wǎng)站制作、建站公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)