9.22 定义上下文管�器的简�方法¶
问题¶
ä½ æƒ³è‡ªå·±åŽ»å®žçŽ°ä¸€ä¸ªæ–°çš„ä¸Šä¸‹æ–‡ç®¡ç�†å™¨ï¼Œä»¥ä¾¿ä½¿ç”¨withè¯å�¥ã€‚
解决方案¶
实现一个新的上下文管ç�†å™¨çš„æœ€ç®€å�•的方法就是使用 contexlib 模å�—ä¸çš„ @contextmanager 装饰器。
下é�¢æ˜¯ä¸€ä¸ªå®žçŽ°äº†ä»£ç �å�—计时功能的上下文管ç�†å™¨ä¾‹å�:
import time
from contextlib import contextmanager
@contextmanager
def timethis(label):
start = time.time()
try:
yield
finally:
end = time.time()
print('{}: {}'.format(label, end - start))
# Example use
with timethis('counting'):
n = 10000000
while n > 0:
n -= 1
在函数 timethis() ä¸ï¼Œyield 之å‰�的代ç �会在上下文管ç�†å™¨ä¸ä½œä¸º __enter__() 方法执行,
所有在 yield 之å�Žçš„代ç �会作为 __exit__() 方法执行。
如果出现了异常,异常会在yieldè¯å�¥é‚£é‡ŒæŠ›å‡ºã€‚
下é�¢æ˜¯ä¸€ä¸ªæ›´åŠ é«˜çº§ä¸€ç‚¹çš„ä¸Šä¸‹æ–‡ç®¡ç�†å™¨ï¼Œå®žçŽ°äº†åˆ—è¡¨å¯¹è±¡ä¸Šçš„æŸ�ç§�事务:
@contextmanager
def list_transaction(orig_list):
working = list(orig_list)
yield working
orig_list[:] = working
这段代ç �的作用是任何对列表的修改å�ªæœ‰å½“所有代ç �è¿�行完æˆ�并且ä¸�出现异常的情况下æ‰�会生效。 下é�¢æˆ‘们æ�¥æ¼”示一下:
>>> items = [1, 2, 3]
>>> with list_transaction(items) as working:
... working.append(4)
... working.append(5)
...
>>> items
[1, 2, 3, 4, 5]
>>> with list_transaction(items) as working:
... working.append(6)
... working.append(7)
... raise RuntimeError('oops')
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: oops
>>> items
[1, 2, 3, 4, 5]
>>>
讨论¶
通常情况下,如果è¦�写一个上下文管ç�†å™¨ï¼Œä½ 需è¦�定义一个类,里é�¢åŒ…å�«ä¸€ä¸ª __enter__() 和一个
__exit__() 方法,如下所示:
import time
class timethis:
def __init__(self, label):
self.label = label
def __enter__(self):
self.start = time.time()
def __exit__(self, exc_ty, exc_val, exc_tb):
end = time.time()
print('{}: {}'.format(self.label, end - self.start))
尽管这个也�难写,但是相比较写一个简�的使用 @contextmanager 注解的函数而言还是�显�味。
@contextmanager 应该仅仅用�写自包�的上下文管�函数。
å¦‚æžœä½ æœ‰ä¸€äº›å¯¹è±¡(比如一个文件ã€�网络连接或é”�),需è¦�支æŒ� with è¯å�¥ï¼Œé‚£ä¹ˆä½ 就需è¦�å�•独实现
__enter__() 方法和 __exit__() 方法。