1. 程式人生 > >Python yield使用詳解(二)

Python yield使用詳解(二)

上下文管理器和with塊

with表示式

常見的with用法格式:

    with open(filename) as f:
        statement
        statement
     ...
    with lock:
        statement
        statement
     ...
  • 控制程式碼塊的進入/退出

定製你自己的上下文管理器

一個計時器的例子:

import time
from contextlib import contextmanager

@contextmanager
def timethis(label):
    start = time.time()
    try:
        yield 
    finally:
        end = time.time()
        print('%s: %0.3f' % (label, end-start))
        
#Usage
with timethis('counting'):
     n = 1000000
     while n > 0:
         n -= 1
#Output
counting: 0.156

另外一個例子:臨時資料夾

import tempfile, shutil
from contextlib import contextmanager
@contextmanager
def tempdir():
    outdir = tempfile.mkdtemp()
    try:
        yield outdir
    finally:
        shutil.rmtree(outdir)
        
#Example
with tempdir() as dirname:
    ...

等等!!!
這裡的yield outdir是什麼?

  • 不是迭代
  • 不是資料流
  • 不是併發
  • 那是什麼???

深入上下文管理器

現在可以對上面內容進行小結一下:

  • 上下文物件存在的目的是管理with語句,就像迭代器的存在是為了管理for語句
  • with語句的目的是簡化ttry/finally模式,這種模式用於保證一段程式碼執行完畢後執行某項操作。即使那段程式碼由於異常,return語句或sys.exit()呼叫而終止,也會執行指定操作

上下文管理器的內部實現:

 

  • 上下文管理器協議包含enterexit兩個方法,with語句還是執行時,會在上下文管理器物件上呼叫enter方法。with語句結束後,會在上下文管理器物件上呼叫exit
    方法,以此扮演finally子句的角色

實現模板

class Manager(object):
    def __enter__(self):
        return value
    def __exit__(self, exc_type, val, tb):
        if exc_type is None:
            return
        else:
            # Handle an exception (if you want)
           return True if handled else False
           
 # Usage         
with Manager() as value:
    statements
    statements

例項

import tempfile
import shutil
class tempdir(object):
    def __enter__(self):
        self.dirname = tempfile.mkdtemp()  #生成臨時資料夾
        return self.dirname
    def __exit__(self, exc, val, tb):
        shutil.rmtree(self.dirname)   #刪除資料夾
    
# Usage  
with tempdir() as dirname:
...
# with語句執行完畢後,會自動刪除那個臨時資料夾

更簡潔的一種選擇:利用@contextmanager裝飾器

import tempfile, shutil
from contextlib import contextmanager
@contextmanager
def tempdir():
    dirname = tempfile.mkdtemp()
    try:
        yield dirname
    finally:
        shutil.rmtree(dirname)
 # 跟上個例子相同的程式碼。

contextmanager裝飾器執行原理

下圖:

 

  • 思考剪刀處yield程式碼
  • 將程式碼一分兩半

  • 每一半對應著上下文管理器協議
  • yield是促成圖中這一實現的魔法

這裡有一個注意點:使用@contextmanager裝飾器時,要把yield語句放在try/finally語句中,這是無法避免的,因為我們永遠不知道上下文管理器的使用者會在with中做什麼(會引發一些python直譯器能捕獲到的錯誤)。
當然,想要你如果想要更加深入的瞭解@contextmanager的內部程式碼實現,可以檢視原始碼,這裡不展開了。

總結

  1. yield表示式的另一個不同作用:上下文管理器
  2. 常用來重新定製控制流
  3. 也可以用@contextmanager裝飾器來代替enterexit兩個方法。優雅且實用,把三個不同的Python特性結合到一起: 函式裝飾器,生成器和with語句

參考資料

David beazley協程
Fluent Python



作者:尋找無雙丶
連結:https://www.jianshu.com/p/bf887cae4d8e
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。