深入理解Python的With-as语句
學習Python有一段時間了,最近做一個項目會涉及到文件的讀取和關閉。比如:我想把一些對象序列化到文件里面,然后當我再次使用的時候,在從文件里面讀取反序列化成對象。像這種操作一般都是用try…except…finally。但是經過自己對Python的研究發現會有更出色的方法,比如:with-as語句也有的人稱為context manager。
With-as 用法
我們先看一下例子,當我們需要打開一個文件的時,比如:txt等,一般經常會這么操作:
try:f = file.open('test.txt','rw')To Do except:To Do finally:f.close()這是錯誤,因為file.open是否打開文件是不確定,而在出現異常的時候你卻關閉了已經打開的文件。文件沒有打開怎么能直接關閉呢?你可以按照下面的解決方法來解決上述出現的問題。
try: f = file.open('test.txt','rw')To Do except: To Do//出現異常直接返回或者退出,這說明file并沒有打開。return/exit(-1) //已經成功打開file文件,所以你需要在finally中關閉打開的文件。 try: To Do except: To Do finally: f.close()你會發現這么做會非常麻煩,并且try……except…..finally嵌套也比較啰嗦。那有沒有好的解決辦法能解決上述問題,并且還能減少代碼量呢?(類似于C#中的using關鍵字)答案是肯定的,那就是with……as語句。With語句適用于對I/O、文件流、數據流等資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的“清理”操作,釋放資源.比如文件使用后自動關閉、線程中鎖的自動獲取和釋放等等。 我們來看一下with-as的用法:
with open('/etc/passwd', 'r') as f: for line in f:print line... more processing code ...這個語句執行完成之后,不管在處理文件過程中是否發生異常,都能保證 with 語句執行完畢后已經關閉了打開的文件句柄,確實比try……except……finally好多了。在這個例子中f就是上下文管理器enter()的返回值,返回的是當前文件自身的引用。Python內建對象都加入了對上下文管理器的支持,可以用在with語句中。比如:file、 threading、decimal等等,在多線程模塊中,lock和條件變量也是支持with語句的。例如:
lock = threading.Lock() with lock: # Critical section of code...在代碼執行之前lock總是先獲得,只要block代碼完成lock就會被釋放。要想徹底了解Python的With-As語句,請繼續往下看。
Python術語
Context Management Protocol(上下文管理協議):包含方法__enter()__和__exit()__,支持該協議的對象要實現這兩個方法。上下文管理器(Context Manager):支持上下文管理協議的對象,這種對象實現了__enter()__ 和__exit()__ 方法。上下文管理器定義執行 with 語句時要建立的運行時上下文,負責執行 with 語句塊上下文中的進入與退出操作。通常使用 with 語句調用上下文管理器,也可以通過直接調用其方法來使用。
context_manager = context_expression exit = type(context_manager).__exit__ value = type(context_manager).__enter__(context_manager) # True 表示正常執行,即便有異常也忽略;False 表示重新拋出異常,需要對異常進行處理. exc = True try:target = value # 如果使用了 as 子句with-body # 執行 with-body except:# 執行過程中有異常發生exc = False# 如果 __exit__ 返回 True,則異常被忽略;如果返回 False,則重新拋出異常# 由外層代碼對異常進行處理if not exit(context_manager, *sys.exc_info()):raise finally:# 正常退出,或者通過 statement-body 中的 break/continue/return 語句退出# 或者忽略異常退出if exc:exit(context_manager, None, None, None) # 缺省返回 None,None 在布爾上下文中看做是 False上下文管理協議
with表達式執行生成一個叫做上下文管理器的對象,上下文管理器必須包含enter()和exit()方法,并且要實現該兩個方法。 上下文管理器的enter()方法被調用,返回值將賦值給var,如果沒有as var,則返回值被丟棄。 執行With-Body語句體。 不管是否執行過程中是否發生了異常,執行上下文管理器的 exit() 方法,exit() 方法負責執行“clean-up”工作,如釋放資源等。如果執行過程中沒有出現異常,或者語句體中執行了語句( break/continue/return),則以 None 作為參數調用 exit(None, None, None) ;如果執行過程中出現異常,則使用 sys.excinfo 得到的異常信息為參數調用 exit(exctype, excvalue, exctraceback),通常返回值是一個tuple, (type, value/message, traceback)。 出現異常時,如果 exit(type, value, traceback) 返回 False,則會重新拋出異常,讓with 之外的語句邏輯來處理異常,這也是通用做法;如果返回 True,則忽略異常,不再對異常進行處理。 運行時上下文(runtime context):通過上下文管理器創建,并由上下文管理器的 enter() 和exit() 方法實現,enter() 方法在語句體執行之前進入運行時上下文,exit() 在語句體執行完后從運行時上下文退出。返回一個布爾值表示是否對發生的異常進行處理。如果退出時沒有發生異常,則3個參數都為(None,None,None)。如果發生異常,返回True :不處理異常,否則會在退出該方法后重新拋出異常以由 with 語句之外的代碼進行處理。如果該方法內部產生異常,不能重新拋出通過參數傳遞進來的異常,只需要return False 就可以。之后,上下文管理代碼會檢測是否 exit() 失敗來捕獲和處理異常。
#-*- coding: utf-8 -*-class Cursor(object): def execute(self,msg):print msgclass DatabaseConnection(object): def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果沒有異常,則提交事務print "Exited Without Exception"self.commit()else:#如果有異常,則回滾print "Exited with exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()if __name__=="__main__": db_connection = DatabaseConnection()with db_connection as cursor:cursor.execute("insert into......")cursor.execute("delete from......")代碼運行效果如下:
Go into __enter__() insert into...... delete from...... Go into __exit__() Exited Without Exception Commits current transaction上述代碼正好驗證了我們之前的分析,當運行with dbconnection運行時,進入我們自定義的__enter()__方法,當執行完with包裹的代碼塊時,就會進入__exit()__方法,如果沒有異常(通過exctb是否為None來判斷,當然也可以用其他兩個參數判斷。)則執行相應的代碼邏輯。
#!/usr/bin/env python #-*- coding: utf-8 -*-class Cursor(object): def execute(self,msg):print msgclass DatabaseConnection(object): def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果沒有異常,則提交事務print "Exited Without Exception"self.commit()else:#如果有異常,則回滾print "Exited With Exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()if __name__=="__main__": db_connection = DatabaseConnection()with db_connection as cursor:cursor.execute("insert into......")raise Exception("raise exception")cursor.execute("delete from......")該代碼示例中,我在with包裹的代碼塊中造成一個異常。我們來看一下效果:
Go into __enter__() insert into...... Go into __exit__() Exited With Exception raised type:[ <type 'exceptions.Exception'> ],value:[ raise exception ],exc_tb:[ <traceback object at 0x0252A878> ] Rolls back current transaction Traceback (most recent call last): File "D:\Test\DatabaseConnection.py", line 34, in <module>raise Exception("raise exception") Exception: raise exception當with包裹的代碼塊一旦出現異常,則進入exit()方法內,并根據該方法的參數全不為None。如果你在exit方法內你不手動返回一個值的話,則默認返回False。如果你返回True,則不會捕捉該異常,即使你在with代碼塊最外面包裹一個try……except…finally也不會捕捉到該異常,如果返回False則with之外的try–except也能捕捉到。
#!/usr/bin/env python #-*- coding: utf-8 -*-import sysclass Cursor(object): def execute(self,msg):print msgclass DatabaseConnection(object): def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"def __enter__(self):print "Go into __enter__()"cursor = Cursor()return cursordef __exit__(self,exc_type,exc_value,exc_tb):print "Go into __exit__()"#raise Exception("__exit__......Exception")if exc_tb is None:#如果沒有異常,則提交事務print "Exited Without Exception"self.commit()else:#如果有異常,則回滾print "Exited With Exception raised"print "type:[",exc_type,"],value:[",exc_value,"],exc_tb:[",exc_tb,"]"self.rollback()return Trueif __name__=="__main__": db_connection = DatabaseConnection()try:with db_connection as cursor:cursor.execute("insert into......")raise Exception("raise exception")cursor.execute("delete from......")except:print"包裹with語句的try ",sys.exc_info()運行效果如下:
Go into __enter__() insert into...... Go into __exit__() Exited With Exception raised type:[ <type 'exceptions.Exception'> ],value:[ raise exception ],exc_tb:[ <traceback object at 0x0252A878> ] Rolls back current transaction基本上Python的常用的用法我就了解這么多,至于代碼希望你動手試一下你也能了解Python with語句的原理.
The contextlib module
contextlib模塊支持一些函數和裝飾器,比如:裝飾器 contextmanager、函數 nested 和上下文管理器closing。使用這些對象,可以對已有的生成器(yield)函數或者對象進行包裝,加入對上下文管理協議的支持,這樣可以避免專門編寫上下文管理器來支持 with 語句。
contextmanager
contextmanager 用于對生成器(yield)函數進行裝飾,生成器(yield)函數被裝飾以后,返回的是一個ContextManager(上下文管理器),其 __enter()__ 和 __exit()__ 方法由 contextmanager 負責提供,而不是之前通過一個上下文管理器重寫這兩個方法了。被裝飾的函數只能產生一個值,否則會導致異常 RuntimeError;并且會把yield的值賦值給as后面的變量。看一下例子:
#!/usr/bin/env python #-*- coding: utf-8 -*-import sys from contextlib import contextmanagerclass Cursor(object): def execute(self,msg):print msgclass DatabaseConnection(object): def cursor(self):print "Create a instance of Cursor"return Cursor()def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"@contextmanager def db_transaction(connection): cursor = connection.cursor()try:print "yeild 執行之前......"yield cursorprint "yeild 執行之后......"except:print "db_transaction raise exception"connection.rollback()else:print"Existed without exception"connection.commit()if __name__=="__main__": db_connection = DatabaseConnection()try:with db_transaction(db_connection) as cursor:cursor.execute("insert into......")#raise Exception("raise exception")cursor.execute("delete from......")except:print sys.exc_info()運行結果如下:
Create a instance of Cursor yeild 執行之前...... insert into...... delete from...... yeild 執行之后...... Existed without exception Commits current transaction通過上述運行結果,可以看出,生成器函數中 yield 之前的語句在 enter() 方法中執行,yield 之后的語句在__exit()__ 中執行,而 yield 產生的值賦給了 as 后面的 變量。這個contextmanager修飾器 只是省略了 __enter()__ / __exit()__ 的編寫,但并不負責實現“獲取資源”和“清理資源”工作;“獲取資源”操作需要定義在 yield 語句之前,“清理資源”操作需要定義 yield 語句之后,這樣 with 語句在執行__enter()__ / __exit()__ 方法時會執行這些語句以獲取/釋放資源,即生成器函數中需要實現必要的邏輯控制,包括資源訪問出現錯誤時拋出適當的異常。 nested(mgr1, mgr2, …)() contextlib模塊也有個nested(mgr1, mgr2, …)()函數,這個函數可以作用在多個上下文管理器。例如下面的例子with 語句不僅開啟一個transaction也獲得了一個線程鎖,讓當前操作不被其他線程干擾。
#!/usr/bin/env python #-*- coding: utf-8 -*-import sys from contextlib import contextmanager,nested,closing import threadingclass Cursor(object): def execute(self,msg):print msgclass DatabaseConnection(object): def cursor(self):print "Create a instance of Cursor"return Cursor()def commit(self):print "Commits current transaction"def rollback(self):print "Rolls back current transaction"@contextmanager def db_transaction(connection): cursor = connection.cursor()try:print "yeild 執行之前......"yield cursorprint "yeild 執行之后......"except:print "db_transaction raise exception"connection.rollback()else:print"Existed without exception"connection.commit()if __name__=="__main__": db_connection = DatabaseConnection()lock = threading.Lock()with nested(db_transaction(db_connection),lock) as (cursor,locked):cursor.execute("insert into......")cursor.execute("delete from......") 運行結果如下:Create a instance of Cursor yeild 執行之前...... insert into...... delete from...... yeild 執行之后...... Existed without exception Commits current transactionclosing(object)()方法返回一個對象可以綁定到as后面的變量,保證了打開的對象在 with-body(with包裹的代碼) 執行完后會關閉掉。closing 上下文管理器包裝起來的對象必須提供 close() 方法的定義,否則執行時會報錯誤。closing 適用于提供了 close() 實現的對象,比如:網絡連接、數據庫連接有非常的用武之地,也可以在自定義類時通過接口 close() 來執行所需要的資源“清理”工作。當然上述的這些操作你完全可以按照自己的邏輯去執行。請看如下代碼:
import urllib, sys from contextlib import closingwith closing(urllib.urlopen('http://www.yahoo.com')) as f: for line in f:sys.stdout.write(line)基本上python的With語句的理解到此結束。希望本文對你有用,如有用請推薦。
參考文獻
?
- 淺談 Python 的 with 語句 這篇文章寫得不錯,里面的術語解釋的很到位,本文中用到的術語多來源于這篇文章。
- Python 2.6 官方介紹言簡意賅突出主題,文中的例子來源于這篇文章。自己把它實現了啊。
- 理解Python中的with…as…語法可以參考一下,這篇文章作者介紹的還可以,但是沒有給出更全面的介紹沒有第一個文章中術語介紹的詳細。
- 理解Python的with語句只是一般介紹沒有深入。
原文來源:https://cloud.tencent.com/developer/article/1083148
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的深入理解Python的With-as语句的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈 Python 的 with 语句
- 下一篇: AI时代!我选Python因为Pytho