項目地址:https://git.io/pytipshtml
Python 2.5 引入了 with
語句(PEP 343)與上下文管理器類型(Context Manager Types),其主要做用包括:python
保存、重置各類全局狀態,鎖住或解鎖資源,關閉打開的文件等。With Statement Context Managersgit
一種最廣泛的用法是對文件的操做:github
with open("utf8.txt", "r") as f: print(f.read())
你好,世界!
上面的例子也能夠用 try...finally...
實現,它們的效果是相同(或者說上下文管理器就是封裝、簡化了錯誤捕捉的過程):spa
try: f = open("utf8.txt", "r") print(f.read()) finally: f.close()
你好,世界!
除了文件對象以外,咱們也能夠本身建立上下文管理器,與 0x01 中介紹的迭代器相似,只要定義了 __enter__()
和 __exit__()
方法就成爲了上下文管理器類型。with
語句的執行過程以下:code
執行 with
後的語句獲取上下文管理器,例如 open('utf8.txt', 'r')
就是返回一個 file object
;orm
加載 __exit__()
方法備用;htm
執行 __enter__()
,該方法的返回值將傳遞給 as
後的變量(若是有的話);對象
執行 with
語法塊的子句;ip
執行 __exit__()
方法,若是 with
語法塊子句中出現異常,將會傳遞 type, value, traceback
給 __exit__()
,不然將默認爲 None
;若是 __exit__()
方法返回 False
,將會拋出異常給外層處理;若是返回 True
,則忽略異常。
瞭解了 with
語句的執行過程,咱們能夠編寫本身的上下文管理器。假設咱們須要一個引用計數器,而出於某些特殊的緣由須要多個計數器共享全局狀態而且能夠相互影響,並且在計數器使用完畢以後須要恢復初始的全局狀態:
_G = {"counter": 99, "user": "admin"} class Refs(): def __init__(self, name = None): self.name = name self._G = _G self.init = self._G['counter'] def __enter__(self): return self def __exit__(self, *args): self._G["counter"] = self.init return False def acc(self, n = 1): self._G["counter"] += n def dec(self, n = 1): self._G["counter"] -= n def __str__(self): return "COUNTER #{name}: {counter}".format(**self._G, name=self.name) with Refs("ref1") as ref1, Refs("ref2") as ref2: # Python 3.1 加入了多個並列上下文管理器 for _ in range(3): ref1.dec() print(ref1) ref2.acc(2) print(ref2) print(_G)
COUNTER #ref1: 98 COUNTER #ref2: 100 COUNTER #ref1: 99 COUNTER #ref2: 101 COUNTER #ref1: 100 COUNTER #ref2: 102 {'user': 'admin', 'counter': 99}
上面的例子很彆扭可是能夠很好地說明 with
語句的執行順序,只是每次定義兩個方法看起來並非很簡潔,一如既往地,Python 提供了 @contextlib.contextmanager
+ generator
的方式來簡化這一過程(正如 0x01 中 yield
簡化迭代器同樣):
from contextlib import contextmanager as cm _G = {"counter": 99, "user": "admin"} @cm def ref(): counter = _G["counter"] yield _G _G["counter"] = counter with ref() as r1, ref() as r2: for _ in range(3): r1["counter"] -= 1 print("COUNTER #ref1: {}".format(_G["counter"])) r2["counter"] += 2 print("COUNTER #ref2: {}".format(_G["counter"])) print("*"*20) print(_G)
COUNTER #ref1: 98 COUNTER #ref2: 100 COUNTER #ref1: 99 COUNTER #ref2: 101 COUNTER #ref1: 100 COUNTER #ref2: 102 ******************** {'user': 'admin', 'counter': 99}
這裏對生成器的要求是必須只能返回一個值(只有一次 yield
),返回的值至關於 __enter__()
的返回值;而 yield
後的語句至關於 __exit__()
。
生成器的寫法更簡潔,適合快速生成一個簡單的上下文管理器。
除了上面兩種方式,Python 3.2 中新增了 contextlib.ContextDecorator
,能夠容許咱們本身在 class
層面定義新的」上下文管理修飾器「,有興趣能夠到官方文檔查看。至少在我目前看來好像並無帶來更多方便(除了能夠省掉一層縮進以外:()。
上下文管理器的概念與修飾器有不少類似之處,可是要記住的是 with
語句的目的是爲了更優雅地收拾殘局而不是替代 try...finally...
,畢竟在 The Zen of Python 中,
Explicit is better than implicit.
比
Simple is better than complex.
更重要:P。
歡迎關注公衆號 PyHub!