[python] 上下文管理器

什麼是上下文管理器

簡單來說,上下文管理器的目的就是規定對象的使用範圍,如果超出範圍就採取相應“處理”;比如:

f = open('filename.txt')
data = f.read()
f.close()

打開一個文件,讀取文件內容,關閉文件。正常的業務中,這樣寫是不夠的,因爲在操作資源的時候有可能出現錯誤,爲了加強代碼健壯性,需要經常改爲:​​​​​​​

try:
    f = open('filename.txt')
    data = f.read()
except:
    pass
finally:
    f.close()

不管出現什麼錯誤,最後都要關閉和釋放資源f.close(),爲了代碼更具可讀性,且不容易出錯,就會用with實現上下文管理器:​​​​​​​

with open('filename.txt') as f:
    data = f.read()

先理清幾個概念:​​​​​​​

1. 上下文表達式:with open('filename.txt') as f:2. 上下文管理器:open('filename.txt')3. f 不是上下文管理器,應該是資源對象,是上下文管理器返回的對象

實現上下文管理器

要自己實現這樣一個上下文管理,要先知道上下文管理協議。簡單點說,就是在一個類裏,實現了__enter__和__exit__的方法,這個類的實例就是一個上下文管理器。​​​​​​​

class MyResource:
    def __enter__(self):
        print('opening resource......')
        return self

    def operate(self):
        print('doing something......')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('closing resource......')


with MyResource() as r:
    r.operate()

當打開資源的時候進入__enter__方法,return的對象會賦給as對象,即這裏return回self對象會賦值給r;不管有沒有出錯,執行完with代碼塊後都會進入到__exit__方法,如果出錯,__exit__的參數就會被賦值。

業務實戰

實現一個連接mysql數據庫的上下文管理器,就以上面的代碼爲基礎​​​​​​​

import pymysql


class MyResource:
    
    def __init__(self, database):
        self.database = database
    
    def __enter__(self):
        self.conn = pymysql.connect(
            host='localhost',
            port=3306,
            user='root',
            password='xxxxxx',
            database=self.database,
            charset='utf8'
        )
        self.cursor = self.conn.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.commit()
        self.cursor.close()
        self.conn.close()


with MyResource('datatbase') as db:
    db.execute('update test set name="rhys" where id=1')

用with實例化MyResource對象,進入__enter__函數連接數據庫,返回遊標cursor給db,db.execute進行update操作,在退出with代碼塊的時候進入__exit__函數,進行commit操作再關閉遊標和連接。

contextmanager實現上下文管理器

還有另一種方式實現一個上下問管理器,就是用contextlib裏的contextmanager裝飾器:​​​​​​​

from contextlib import contextmanager
import pymysql


@contextmanager
def mysqldb(database):
    try:
        conn = pymysql.connect(
            host='localhost',
            port=3306,
            user='root',
            password='xxxxxx',
            database=database,
            charset='utf8'
        )
        cursor = conn.cursor()
        yield cursor
        conn.commit()
    except Exception as e:
        print(e)
    finally:
        cursor.close()
        conn.close()


with mysqldb('database') as db:
    db.execute('update test set name="rhys" where id=1')

被contextmanager裝飾的函數會成爲一個上下文管理器,用yield返回cursor給as後面的db,執行完update操作退出with代碼塊的時候再回到yield的位置,執行之後的代碼。

個人更喜歡使用contexmanager來實現上下問管理器,代碼更簡便更容易編寫,也更好理解。

 

關注公衆號:日常bug,適合技術點滴積累,利用瑣碎時間學習技術的人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章