python3 中的上下文管理器
上下文管理器 是什麼
只要 實現了 __exit__ , __enter__ 的類 它就是上下文管理器. 它可以用來管理上下文
實現了 __exit__ , __enter__ 魔術 方法 的對象 可以使用 with 語句
上下文表達式 返回一個 上下文管理器
上下文管理的作用 和 目的
上下文管理 對象 是爲了存在的目的 是管理 with 語句, 而 with 語句 目的是爲了 簡化 tyr/ finally 這種模式
這種 模式 保證 運行 一段代碼後, ,即便代碼裏面發生錯誤, return 語句或者調用 終止 sys.exit() , 也會執行特定的代碼段, 來做一些最後的處理 , 比如 釋放連接, 還原一些狀態,釋放資源等 .
介紹 上下文管理器 協議
首先 要實現一個上下文管理 需要 實現兩個 魔術方法 _exit_ , __enter__ , 即需要在一個類中
不管以哪種方式 退出 with 語句, 都會進行 __enter__ 方法裏面的代碼段 , 而不是 __enter__ 返回對象的對象上調用
下面 簡單實現 一個 上下文管理器
Resource 類中實現了兩個魔術方法 , 同時實現了一個query 方法
# -*- coding: utf-8 -*-
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 query(self):
print("query data ...")
with Resource() as r:
r.query()
首先 Resource 實現了 這兩個魔術方法 , 它就是一個上下文管理器 , 就可以使用with 這種語法 ,with 表達式後面一定要是一個 上下文管理器,才能夠這樣寫.
結果如下 :
整個代碼的執行流程
可以看出 首先 執行的 _enter_ 裏面的代碼段 , 後來 返回 self , 然後執行 query 方法 , 最後 執行 _exit_ 魔術方法 .
這個就是最簡單的上下文管理器了.
enter 方法介紹
你可能 會疑惑 爲啥 _enter_ 方法要返回 self ?
這個例子中 返回 self , 即返回 Resource 對象的實例 , 然後通過 r.query 來調用 實例 方法 query , 所以 這裏是需要返回self . 這裏 __enter__ 方法 的返回值 ,
with Resource() as r:pass
就是這段代碼as 後對應變量的值 , 這裏 命名爲 r 它的值就是 self
下面 在 __enter__ 返回 frank ,之後打印r 來看一下 這個 值是什麼 ?
# -*- coding: utf-8 -*-
class Resource:
def __enter__(self):
print("connect to resource")
return "frank"
def __exit__(self, exc_type, exc_val, exc_tb):
print("close resource connection")
def query(self):
print("query data ...")
with Resource() as r:
print(f"r:{r}")
結果如下:
現在 應該理解 爲啥要 返回 self了吧 . 一般 情況下 _enter_ 方法會返回 self , 當然 也可以不返回 .
如果實在不需要返回 , 也可以不返回 .
只要 明白 只要執行 with 這種裏面的 代碼段 首先 是先執行 _enter_ 方法 裏面 的代碼即可.
exit 方法介紹
__exit__ 方法 實在with 裏面 代碼段執行完後 , 執行的方法 ,一般就是 資源清理的代碼,會寫在這裏, 還有一些異常處理的代碼,也可以寫在這裏.
注意到 上面 方法 有 三個參數 , 這三個參數 只有 with 語句報錯後, 這幾個參數 纔會有值, 如果with 代碼段裏面 沒有報錯 那麼 這個三個值 均爲 None
exc_type 異常類
exc_value 異常值
exc_tb traceback 對象
# -*- coding: utf-8 -*-
class Resource:
def __enter__(self):
print("connect to resource")
return "frank"
pass
def __exit__(self, exc_type, exc_val, exc_tb):
"""
:param exc_type: 異常類
:param exc_val: 異常值
:param exc_tb: traceback 對象
:return:
"""
print(exc_type,exc_val,exc_tb)
print("close resource connection")
def query(self):
print("query data ...")
with Resource() as r:
1/0
print(f"r:{r}")
在with 語句 故意拋出一個異常 可以看出 這三個值 .
可以看出 程序就報錯了, 並且異常被拋出來 了.
剛剛在 __exit__ 方法 裏面 其實 是可以處理 這種異常的, 保證 程序可以正常執行.
可以通過 exc_tb 是否爲空 來處理這個異常, 然後 注意這個時候要返回一個True , 這裏的意思是 程序 異常已經處理, 不繼續拋出到主 程序了.
# -*- coding: utf-8 -*-
class Resource:
def __enter__(self):
print("connect to resource")
return "frank"
pass
def __exit__(self, exc_type, exc_val, exc_tb):
"""
:param exc_type: 異常類
:param exc_val: 異常值
:param exc_tb: traceback 對象
:return: True or False
"""
if exc_tb:
print("catch exception . deal exception")
return True
print("close resource connection")
def query(self):
print("query data ...")
with Resource() as r:
1/0
print(f"r:{r}")
執行結果如下:
這裏 __enter__ 方法 返回值, 決定 是否要將 異常拋出來
__exit__ 這個方法 用來 上下文管理器退出執行的方法, 如果 有異常, 可以在這裏處理,並且返回True, 則異常就不會被拋出來, 如果返回false 異常就會被拋出來. 有主程序 處理該異常.
上下文管理 用法
1 常用的示例
- 比如數據連接 以及關閉的操作
一般連接數據庫 需要以下步驟
數據庫連接, 管理
1 連接數據庫
2 exectue sql
3 釋放連接 con.close()
try:
pass
except:
pass
finally:
pass
比如 可以 像下面的的例子
來實現一個上下文管理器
# -*- coding: utf-8 -*-
import pymysql
class ConnText:
def __enter__(self):
print("begin db connection")
self.conn = self.get_connection()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def get_connection(self):
conn = pymysql.connect()
return conn
def close(self):
self.conn.close()
print("close db connection")
def query(self):
print("query data")
with ConnText() as r:
r.query()
-
文件讀寫 也可以用上下文管理器
一般打開文件後 最後 都需要關閉 文件句柄, 這個時候就可以使用上下文管理了.
with open('/tmp/1.sql') as f
pass
框架裏面使用
其實在很多源碼中也可以看到上下文管理器的用法 ,
比如celery 核心對象 Celery 也實現了上下文管理 器
代碼位於:
python3.x/site-packages/celery/app/base.py
在flask 框架裏面 用了很多上下文管理,比如這個模塊
/python3.x/site-packages/flask/ctx.py
RequestContext
, AppContext
這兩個類 都實現了 上下文 的管理器的協議
總結
本文簡單總結了 上下文管理器的語法,使用python 這種語法 可以寫代碼 看起來 更優雅一些, 更加的 pythonic. 有時候要學會看源代碼來學習.
分享快樂,留住感動. '2019-12-01 23:10:59' --frank