python3 中的上下文管理器

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 表達式後面一定要是一個 上下文管理器,才能夠這樣寫.

結果如下 :

img1

整個代碼的執行流程

可以看出 首先 執行的 _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}")

結果如下:

img2

現在 應該理解 爲啥要 返回 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 語句 故意拋出一個異常 可以看出 這三個值 .

img3

img4

可以看出 程序就報錯了, 並且異常被拋出來 了.

剛剛在 __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}")

執行結果如下:

img5

這裏 __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

img7

在flask 框架裏面 用了很多上下文管理,比如這個模塊

/python3.x/site-packages/flask/ctx.py

RequestContext , AppContext 這兩個類 都實現了 上下文 的管理器的協議

img7

總結

本文簡單總結了 上下文管理器的語法,使用python 這種語法 可以寫代碼 看起來 更優雅一些, 更加的 pythonic. 有時候要學會看源代碼來學習.

readocs- python3 中的上下文管理器

分享快樂,留住感動. '2019-12-01 23:10:59' --frank
發佈了112 篇原創文章 · 獲贊 65 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章