1. 程式人生 > >《Python高階程式設計》(二)上下文管理器

《Python高階程式設計》(二)上下文管理器

上下文管理器

定義

  • 上下文管理器是一個包裝任意程式碼塊的物件。上下文管理器保證進入上下文管理器時,每次程式碼執行的一致性;當退出上下文管理器時,相關的資源會被正確回收
  • 功能類似於執行try,except和finally關鍵字;被用到最多的就是:作為確保資源被正確清理的一種方式
    與裝飾器的對比 :大多數方面上下文管理器與裝飾器的作用等價
  • 相似:它們都是包裝其他程式碼的工具。
  • 不同:裝飾器包裝用於定義的程式碼塊(函式或類);上下文管理器可以包裝任意格式的程式碼塊

語法

1. with語句

  • 使用with語句和內建函式open都可以進入上下文管理器
      with open('/path/to/filename', 'r') as my_file:
            contents = my_file.read()
  • 從本質上講,實際上是with語句對其後的程式碼進行求值,該表示式會返回一個物件,該物件包含兩個特殊方法__enter__和__exit__;__enter__方法返回的結果會被賦給as關鍵字之後的變數。
  • 注意:在with後的表示式結果沒有被賦給所謂的變數,只有__enter__的返回值會被賦給該變數

2. enter和exit方法

  • with語句的表示式的作用是返回一個遵循特定協議的物件。該物件必須定義一個__enter__和__exit__方法
  • 除了傳統的self參量,__enter__不接受任何其他引數
  • __exit__方法帶3個位置引數(不包括self):異常型別、異常例項、一個回溯。無異常時三個值均為None
  • 上下文管理器必須定義__exit__方法,該方法可以選擇性地處理保證程式碼塊中出現的異常,或者處理其他需要關閉上下文管理器狀態的事情
# 並沒有做什麼工作,僅返回自身和設定其entered變數:進入時True,退出時False
class ContextManager(object):
    def __init__(self):
        self.entered = False
    def __enter__(self):
        self.entered = True
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.entered = False

cm = ContextManager()
print cm.entered   #False 建立例項

# 使用上下文管理器開啟,entered為True,退出時變為False
with cm:  
    print cm.entered  # True
print cm.entered   #False

with ContextManager() as cm:
    print cm.entered   #True

3. 異常處理

  • __exit__方法選擇性地處理包裝程式碼塊中出現的異常,或者處理其他需要關閉上下文管理器狀態的事情。
  • 如果沒有異常,__exit__三個引數值都是None,如果接收一個異常,就有義務處理異常。

何時編寫上下文管理器

  • 資源清理
    示例:開啟PostgreSQL資料庫連線的上下文管理器(未測試)
import psycopg2

        class DBConnection(object):
            def __init__(self, dbname=None, user=None, password=None, host='localhost'):
                self.host = host
                self.dbname = dbname
                self.user = user
                self.password = password
            def __enter__(self):
                self.connection = psycopg2.connect(
                    dbname = self.dbname,
                    host = self.host,
                    user = self.user,
                    password = self.password)
                return self.connection.cursor()
            def __exit__(self, exc_type, exc_instance, exc_traceback):
                self.connection.close()

        with DBConnection(user='root', dbname='puwell_db') as db:
            db.execute('SELECT 1+1')
            db.fetchall()  #[(2,)]
        db.execute('SELECT 1+1')  #出錯 InterfaceError:cursor already closed

  • 避免重複(還沒完全看懂.待續…
    • 傳播異常
    • 終止異常