5.9 ã€è¿›é˜¶ã€‘上下文管ç†å™¨ ======================== å½“ä½ å‡†å¤‡ä»Žä¸€ä¸ªæ–‡ä»¶ä¸è¯»å–内容时,通常æ¥è¯´ï¼Œéƒ½æ˜¯è¿™ä¹ˆå†™çš„。 .. code:: python >>> file=open('test.txt') >>> >>> print(file.readlines()) # 读å–å¹¶æ‰“å° ['hello,python\n'] >>> >>> file.close() # 关闿–‡ä»¶å¥æŸ„ 上é¢è¿™ç§æ–¹æ³•,需è¦ä½ æ‰‹åŠ¨å…³é—æ–‡ä»¶å¥æŸ„,但是很多时候,程åºå‘˜æ˜¯ä¼šå¿˜è®°è¿™ä¸€æ“作的。 å› ä¸ºæŽ¨èä½ ä½¿ç”¨ä¸‹é¢è¿™ç§æ–¹æ³•,使用 ``with`` 这个关键å—,å¯ä»¥åœ¨æ–‡ä»¶è¯»å–结æŸåŽï¼Œè‡ªåЍ关闿–‡ä»¶å¥æŸ„。 .. code:: python with open('test.txt') as file: print(file.readlines()) 使用 Python çš„ä¸“ä¸šæœ¯è¯æ¥è¯´ï¼Œ\ ``with`` 的这个用法å«åš ``上下文管ç†å™¨``\ 。 1. 什么是上下文管ç†å™¨ï¼Ÿ ----------------------- **åŸºæœ¬è¯æ³•** .. code:: python with EXPR as VAR: 代ç å— ä»Žä¸Šé¢è¿™ä¸ªè¯æ³•ä¸ï¼Œå…ˆç†æ¸…å‡ ä¸ªæ¦‚å¿µï¼š 1. 上下文表达å¼ï¼š\ ``with open('test.txt') as file:`` 2. 上下文管ç†å™¨ï¼š\ ``open('test.txt')`` 3. ``file`` 䏿˜¯ä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Œåº”该是资æºå¯¹è±¡ã€‚ 2. 如何写上下文管ç†å™¨ï¼Ÿ ----------------------- è¦æ‰‹åŠ¨å®žçŽ°ä¸€ä¸ªä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Œéœ€è¦ä½ 有对类有一些了解,至少需è¦çŸ¥é“ä»€ä¹ˆæ˜¯ç±»ï¼Œæ€Žä¹ˆå®šä¹‰ç±»ã€‚å¯¹äºŽç±»çš„çŸ¥è¯†ï¼Œæˆ‘æ”¾åœ¨äº†ç¬¬ä¸ƒç« ï¼Œå› æ¤ä½ å¯ä»¥å…ˆå‰å¾€å¦ä¹ ä¸‹ç¬¬ä¸ƒç« çš„çš„ç¬¬ä¸€èŠ‚å†…å®¹ï¼š\ `7.1 类的ç†è§£ä¸Žä½¿ç”¨ <https://python.iswbm.com/c07/c07_01.html>`__ 。 å¦ä¹ 了类的基本知识,想è¦è‡ªå·±å®žçŽ°è¿™æ ·ä¸€ä¸ªä¸Šä¸‹æ–‡ç®¡ç†ï¼Œå°±ç®€å•了。 ä½ åªè¦åœ¨ä¸€ä¸ªç±»é‡Œå®žçŽ°ä¸Šä¸‹æ–‡ç®¡ç†å议,简å•点说,就是在一个类里,定义了\ ``__enter__``\ å’Œ\ ``__exit__``\ 的方法,这个类的实例就是一个上下文管ç†å™¨ã€‚ 例如这个示例: .. code:: python class Resource(): def __enter__(self): print('===connect to resource===') return self def __exit__(self, exc_type, exc_val, exc_tb): print('===close resource connection===') def operate(self): print('===in operation===') with Resource() as res: res.operate() 我们执行一下,通过日志的打å°é¡ºåºã€‚å¯ä»¥çŸ¥é“其执行过程。 :: ===connect to resource=== ===in operation=== ===close resource connection=== 从这个示例å¯ä»¥å¾ˆæ˜Žæ˜¾çš„çœ‹å‡ºï¼Œåœ¨ç¼–å†™ä»£ç æ—¶ï¼Œå¯ä»¥å°†èµ„æºçš„è¿žæŽ¥æˆ–è€…èŽ·å–æ”¾åœ¨\ ``__enter__``\ ä¸ï¼Œè€Œå°†èµ„æºçš„å…³é—写在\ ``__exit__`` ä¸ã€‚ 3. 为什么需è¦ä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Ÿ --------------------------- å¦ä¹ æ—¶å¤šé—®è‡ªå·±å‡ ä¸ªä¸ºä»€ä¹ˆï¼Œå…»æˆå¯¹ä¸€äº›ç»†èŠ‚çš„æ€è€ƒï¼Œæœ‰åŠ©äºŽåŠ æ·±å¯¹çŸ¥è¯†ç‚¹çš„ç†è§£ã€‚ 为什么è¦ä½¿ç”¨ä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Ÿ 在我看æ¥ï¼Œè¿™å’Œ Python å´‡å°šçš„ä¼˜é›…é£Žæ ¼æœ‰å…³ã€‚ 1. å¯ä»¥ä»¥ä¸€ç§æ›´åŠ ä¼˜é›…çš„æ–¹å¼ï¼Œæ“作(创建/获å–/释放)资æºï¼Œå¦‚文件æ“ä½œã€æ•°æ®åº“连接; 2. å¯ä»¥ä»¥ä¸€ç§æ›´åŠ ä¼˜é›…çš„æ–¹å¼ï¼Œå¤„ç†å¼‚常; 第一ç§ï¼Œæˆ‘们上é¢å·²ç»ä»¥èµ„æºçš„连接为例讲过了。 而第二ç§ï¼Œä¼šè¢«å¤§å¤šæ•°äººæ‰€å¿½ç•¥ã€‚这里会é‡ç‚¹è®²ä¸€ä¸‹ã€‚ 大家都知é“,处ç†å¼‚常,通常都是使用 ``try...execept..`` æ¥æ•获处ç†çš„ã€‚è¿™æ ·åšä¸€ä¸ªä¸å¥½çš„地方是,在代ç 的主逻辑里,会有大é‡çš„异常处ç†ä»£ç†ï¼Œè¿™ä¼šå¾ˆå¤§çš„影哿ˆ‘们的å¯è¯»æ€§ã€‚ å¥½ä¸€ç‚¹çš„åšæ³•呢,å¯ä»¥ä½¿ç”¨ ``with`` 将异常的处ç†éšè—èµ·æ¥ã€‚ ä»ç„¶æ˜¯ä»¥ä¸Šé¢çš„代ç 为例,我们将\ ``1/0`` 这个\ ``一定会抛出异常的代ç ``\ 写在 ``operate`` 里 .. code:: python class Resource(): def __enter__(self): print('===connect to resource===') return self def __exit__(self, exc_type, exc_val, exc_tb): print('===close resource connection===') return True def operate(self): 1/0 with Resource() as res: res.operate() è¿è¡Œä¸€ä¸‹ï¼ŒæƒŠå¥‡åœ°å‘现,居然ä¸ä¼šæŠ¥é”™ã€‚ 这就是上下文管ç†å议的一个强大之处,异常å¯ä»¥åœ¨\ ``__exit__`` 进行æ•èŽ·å¹¶ç”±ä½ è‡ªå·±å†³å®šå¦‚ä½•å¤„ç†ï¼Œæ˜¯æŠ›å‡ºå‘¢è¿˜æ˜¯åœ¨è¿™é‡Œå°±è§£å†³äº†ã€‚在\ ``__exit__`` 里返回 ``True``\ (没有return 就默认为 return False),就相当于告诉 Pythonè§£é‡Šå™¨ï¼Œè¿™ä¸ªå¼‚å¸¸æˆ‘ä»¬å·²ç»æ•获了,ä¸éœ€è¦å†å¾€å¤–抛了。 在 写\ ``__exit__`` å‡½æ•°æ—¶ï¼Œéœ€è¦æ³¨æ„çš„äº‹ï¼Œå®ƒå¿…é¡»è¦æœ‰è¿™ä¸‰ä¸ªå‚数: - exc_type:异常类型 - exc_val:异常值 - exc_tbï¼šå¼‚å¸¸çš„é”™è¯¯æ ˆä¿¡æ¯ å½“ä¸»é€»è¾‘ä»£ç æ²¡æœ‰æŠ¥å¼‚å¸¸æ—¶ï¼Œè¿™ä¸‰ä¸ªå‚æ•°å°†éƒ½ä¸ºNone。 4. å¦ä¼šä½¿ç”¨ contextlib ---------------------- 在上é¢çš„例åä¸ï¼Œæˆ‘ä»¬åªæ˜¯ä¸ºäº†æž„建一个上下文管ç†å™¨ï¼Œå´å†™äº†ä¸€ä¸ªç±»ã€‚å¦‚æžœåªæ˜¯è¦å®žçŽ°ä¸€ä¸ªç®€å•çš„åŠŸèƒ½ï¼Œå†™ä¸€ä¸ªç±»æœªå…æœ‰ç‚¹è¿‡äºŽç¹æ‚。这时候,我们就想,如果åªå†™ä¸€ä¸ªå‡½æ•°å°±å¯ä»¥å®žçŽ°ä¸Šä¸‹æ–‡ç®¡ç†å™¨å°±å¥½äº†ã€‚ 这个点Python早就想到了。它给我们æä¾›äº†ä¸€ä¸ªè£…é¥°å™¨ï¼Œä½ åªè¦æŒ‰ç…§å®ƒçš„代ç åè®®æ¥å®žçŽ°å‡½æ•°å†…å®¹ï¼Œå°±å¯ä»¥å°†è¿™ä¸ªå‡½æ•°å¯¹è±¡å˜æˆä¸€ä¸ªä¸Šä¸‹æ–‡ç®¡ç†å™¨ã€‚ 我们按照 contextlib çš„åè®®æ¥è‡ªå·±å®žçŽ°ä¸€ä¸ªæ‰“å¼€æ–‡ä»¶ï¼ˆwith open)的上下文管ç†å™¨ã€‚ .. code:: python import contextlib @contextlib.contextmanager def open_func(file_name): # __enter__方法 print('open file:', file_name, 'in __enter__') file_handler = open(file_name, 'r') # ã€é‡ç‚¹ã€‘:yield yield file_handler # __exit__方法 print('close file:', file_name, 'in __exit__') file_handler.close() return with open_func('/Users/MING/mytest.txt') as file_in: for line in file_in: print(line) 在被装饰函数里,必须是一个生æˆå™¨ï¼ˆå¸¦æœ‰yield),而yield之å‰çš„代ç ,就相当于\ ``__enter__``\ 里的内容。yield 之åŽçš„代ç ,就相当于\ ``__exit__`` 里的内容。 上é¢è¿™æ®µä»£ç åªèƒ½å®žçŽ°ä¸Šä¸‹æ–‡ç®¡ç†å™¨çš„第一个目的(管ç†èµ„æºï¼‰ï¼Œå¹¶ä¸èƒ½å®žçŽ°ç¬¬äºŒä¸ªç›®çš„ï¼ˆå¤„ç†å¼‚常)。 如果è¦å¤„ç†å¼‚常,å¯ä»¥æ”¹æˆä¸‹é¢è¿™ä¸ªæ ·å。 .. code:: python import contextlib @contextlib.contextmanager def open_func(file_name): # __enter__方法 print('open file:', file_name, 'in __enter__') file_handler = open(file_name, 'r') try: yield file_handler except Exception as exc: # deal with exception print('the exception was thrown') finally: print('close file:', file_name, 'in __exit__') file_handler.close() return with open_func('/Users/MING/mytest.txt') as file_in: for line in file_in: 1/0 print(line) 好åƒåªè¦è®²åˆ°ä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Œå¤§å¤šæ•°äººéƒ½ä¼šè°ˆåˆ°æ‰“开文件这个ç»å…¸çš„例å。 但是在实际开å‘ä¸ï¼Œå¯ä»¥ä½¿ç”¨åˆ°ä¸Šä¸‹æ–‡ç®¡ç†å™¨çš„例å也ä¸å°‘。我这边举个我自己的例å。 在OpenStackä¸ï¼Œç»™ä¸€ä¸ªè™šæ‹Ÿæœºåˆ›å»ºå¿«ç…§æ—¶ï¼Œéœ€è¦å…ˆåˆ›å»ºä¸€ä¸ªä¸´æ—¶æ–‡ä»¶å¤¹ï¼Œæ¥å˜æ”¾è¿™ä¸ªæœ¬åœ°å¿«ç…§é•œåƒï¼Œç‰åˆ°æœ¬åœ°å¿«ç…§é•œåƒåˆ›å»ºå®ŒæˆåŽï¼Œå†å°†è¿™ä¸ªé•œåƒä¸Šä¼ 到Glance。然åŽåˆ 除这个临时目录。 这段代ç 的主逻辑是\ ``创建快照``\ ,而\ ``创建临时目录``\ ,属于å‰ç½®æ¡ä»¶ï¼Œ\ ``åˆ é™¤ä¸´æ—¶ç›®å½•``\ ,是收尾工作。 虽然代ç é‡å¾ˆå°‘,逻辑也ä¸å¤æ‚,但是“\ ``创建临时目录,使用完åŽå†åˆ 除临时目录``\ â€è¿™ä¸ªåŠŸèƒ½ï¼Œåœ¨ä¸€ä¸ªé¡¹ç›®ä¸å¾ˆå¤šåœ°æ–¹éƒ½éœ€è¦ç”¨åˆ°ï¼Œå¦‚æžœå¯ä»¥å°†è¿™æ®µé€»è¾‘处ç†å†™æˆä¸€ä¸ªå·¥å…·å‡½æ•°ä½œä¸ºä¸€ä¸ªä¸Šä¸‹æ–‡ç®¡ç†å™¨ï¼Œé‚£ä»£ç çš„å¤ç”¨çŽ‡ä¹Ÿå¤§å¤§æé«˜ã€‚ ä»£ç æ˜¯è¿™æ ·çš„ .. image:: http://image.iswbm.com/20190310172800.png 5. æ€»ç»“èµ·æ¥ ----------- 使用上下文管ç†å™¨æœ‰ä¸‰ä¸ªå¥½å¤„: 1. æé«˜ä»£ç çš„å¤ç”¨çŽ‡ï¼› 2. æé«˜ä»£ç 的优雅度; 3. æé«˜ä»£ç çš„å¯è¯»æ€§ï¼›