python关键字详解_Python 中的关键字with详解
在 Python 2.5 中,with關鍵字被加入。它將常用的 try ... except ... finally ...模式很方便的被復用??匆粋€最經典的例子:
with open('file.txt') as f:
content = f.read()
在這段代碼中,無論with中的代碼塊在執行的過程中發生任何情況,文件最終都會被關閉。如果代碼塊在執行的過程中發生了一個異常,那么在這個異常被拋出前,程序會先將被打開的文件關閉。
再看另外一個例子。
在發起一個數據庫事務請求的時候,經常會用類似這樣的代碼:
db.begin()
try:
# do some actions
except:
db.rollback()
raise
finally:
db.commit()
如果將發起事務請求的操作變成可以支持with關鍵字的,那么用像這樣的代碼就可以了:
with transaction(db):
# do some actions
下面,詳細的說明一下with的執行過程,并用兩種常用的方式實現上面的代碼。
with 的一般執行過程
一段基本的with表達式,其結構是這樣的:
with EXPR as VAR:
BLOCK
其中:EXPR可以是任意表達式;as VAR是可選的。其一般的執行過程是這樣的:
計算EXPR,并獲取一個上下文管理器。
上下文管理器的__exit()__方法被保存起來用于之后的調用。
調用上下文管理器的__enter()__方法。
如果with表達式包含as VAR,那么EXPR的返回值被賦值給VAR。
執行BLOCK中的表達式。
調用上下文管理器的__exit()__方法。如果BLOCK的執行過程中發生了一個異常導致程序退出,那么異常的type、value和traceback(即sys.exc_info()的返回值)將作為參數傳遞給__exit()__方法。否則,將傳遞三個None。
將這個過程用代碼表示,是這樣的:
mgr = (EXPR)
exit = type(mgr).__exit__ # 這里沒有執行
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # 如果有 as VAR
BLOCK
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
這個過程有幾個細節:
如果上下文管理器中沒有__enter()__或者__exit()__中的任意一個方法,那么解釋器會拋出一個AttributeError。
在BLOCK中發生異常后,如果__exit()__方法返回一個可被看成是True的值,那么這個異常就不會被拋出,后面的代碼會繼續執行。
接下來,用兩種方法來實現上面來實現上面的過程的吧。
實現上下文管理器類
第一種方法是實現一個類,其含有一個實例屬性db和上下文管理器所需要的方法__enter()__和__exit()__。
class transaction(object):
def __init__(self, db):
self.db = db
def __enter__(self):
self.db.begin()
def __exit__(self, type, value, traceback):
if type is None:
db.commit()
else:
db.rollback()
了解with的執行過程后,這個實現方式是很容易理解的。下面介紹的實現方式,其原理理解起來要復雜很多。
使用生成器裝飾器
在Python的標準庫中,有一個裝飾器可以通過生成器獲取上下文管理器。使用生成器裝飾器的實現過程如下:
from contextlib import contextmanager
@contextmanager
def transaction(db):
db.begin()
try:
yield db
except:
db.rollback()
raise
else:
db.commit()
第一眼上看去,這種實現方式更為簡單,但是其機制更為復雜??匆幌缕鋱绦羞^程吧:
Python解釋器識別到yield關鍵字后,def會創建一個生成器函數替代常規的函數(在類定義之外我喜歡用函數代替方法)。
裝飾器contextmanager被調用并返回一個幫助方法,這個幫助函數在被調用后會生成一個GeneratorContextManager實例。最終with表達式中的EXPR調用的是由contentmanager裝飾器返回的幫助函數。
with表達式調用transaction(db),實際上是調用幫助函數。幫助函數調用生成器函數,生成器函數創建一個生成器。
幫助函數將這個生成器傳遞給GeneratorContextManager,并創建一個GeneratorContextManager的實例對象作為上下文管理器。
with表達式調用實例對象的上下文管理器的__enter()__方法。
__enter()__方法中會調用這個生成器的next()方法。這時候,生成器方法會執行到yield db處停止,并將db作為next()的返回值。如果有as VAR,那么它將會被賦值給VAR。
with中的BLOCK被執行。
BLOCK執行結束后,調用上下文管理器的__exit()__方法。__exit()__方法會再次調用生成器的next()方法。如果發生StopIteration異常,則pass。
如果沒有發生異常生成器方法將會執行db.commit(),否則會執行db.rollback()。
再次看看上述過程的代碼大致實現:
def contextmanager(func):
def helper(*args, **kwargs):
return GeneratorContextManager(func(*args, **kwargs))
return helper
class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
pass
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
if sys.exc_info()[1] is not value:
raise
總結
Python的with表達式包含了很多Python特性。花點時間吃透with是一件非常值得的事情。
一些其他的例子
鎖:
@contextmanager
def locked(lock):
lock.acquired()
try:
yield
finally:
lock.release()
標準輸出重定向:
@contextmanager
def stdout_redirect(new_stdout):
old_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield
finally:
sys.stdout = old_stdout
with open("file.txt", "w") as f:
with stdout_redirect(f):
print "hello world"
引用
總結
以上是生活随笔為你收集整理的python关键字详解_Python 中的关键字with详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3.7下载tensorflo
- 下一篇: python对英语的要求_学python