今天在逛stackoverflow的時候,發現了contextlib這個模塊的的作用!而且今天成功將這個模塊應用到了項目中,簡直爽的飛起!特此整理一篇博客,分享給大家!
一.引言
我們在操作文件時最常用的就是使用with上下文管理器,這樣會讓代碼的可讀性更強而且錯誤更少,例如:
with open('/tmp/a.txt', a) as file_obj: file_obj.write("hello carson")
按照上述這樣寫的好處在於,在執行完畢縮進代碼塊后會自動關閉文件。同樣的例子還有threading.Lock,如果不使用with,需要這樣寫:
import threading lock = threading.Lock() lock.acquire() try: my_list.append(item) finally: lock.release()
如果使用with,那就會非常簡單:
with lock: my_list.append(item)
創建上下文管理實際就是創建一個類,添加__enter__和__exit__方法。下面我們來實現open的上下文管理功能:
class OpenContext(object): def __init__(self, filename, mode): self.fp = open(filename, mode) def __enter__(self): return self.fp def __exit__(self, exc_type, exc_val, exc_tb): self.fp.close() with OpenContext('/tmp/a.txt', 'a') as file_obj: file_obj.write("hello 6666")
上面我們自定義上下文管理器確實很方便,但是Python標准庫還提供了更加易用的上下文管理器工具模塊contextlib,它是通過生成器實現的,我們不需要再創建類以及__enter__和__exit__這兩個特俗的方法:
from contextlib import contextmanager @contextmanager def make_open_context(filename, mode): fp = open(filename, mode) try: yield fp finally: fp.close() with make_open_context('/tmp/a.txt', 'a') as file_obj: file_obj.write("hello carson666")
在上文中,yield關鍵詞把上下文分割成兩部分:yield之前就是__init__中的代碼塊;yield之后其實就是__exit__中的代碼塊,yield生成的值會綁定到with語句as子句中的變量,例如在上面的例子中,yield生成的值是文件句柄對象fp,在下面的with語句中,會將fp和file_obj綁定到一起,也就是說file_obj此時就是一個文件句柄對象,那么它就可以操作文件了,因此就可以調用file_obj.write("hello carson666"),另外要注意的是如果yield沒有生成值,那么在with語句中就不需要寫as子句了,后面會結合具體的例子詳解。
案例一:
# _*_ coding:utf-8 _*_ from contextlib import contextmanager """ contextmanager給了我們一個機會,即將原來不是上下文管理器的類變成了一個 上下文管理器,例如這里的MyResource類 """
class MyResource: def query(self): print("query data")
@contextmanager def make_myresource(): print("connect to resource") yield MyResource() print("connect to resource") with make_myresource() as r: r.query()
上面的例子就充分體現了contextmanager的強大作用,將一個不是上下問管理器的類 MyResource變成了一個上下文管理器,這樣做的好處在於,我們就可以在執行真正的核心代碼之前可以執行一部分代碼,然后在執行完畢后,又可以執行一部分代碼,這種場景在實際需求中還是很常見的。上面yield MyResource() 生成了一個實例對象,然后我們可以在with語句中調用類中的方法。看看最終的打印結果:
上面這樣寫的好處還有:假如MyResource是Flask或者其他第三方插件提供給我們的類庫,如果使用自定義上下文管理器,那么就要使用手動去修改源碼,在原代碼的基礎上添加enter和exit方法,這樣做肯定不合適;現在我們可以在類MyResource的外部使用contextmanager將該類包裝成為一個上下文管理器,這樣既可以調用類中的方法,又可以在執行核心代碼前后再執行一些相關的語句。
案例二需求
我現在想打印 《且將生活一飲而盡》 也就是說我們要打印《 和 》;這就體現了上下文管理器的一個用法:我僅僅就是需要在我的核心代碼前面和后面各執行一段代碼而已:
# _*_ coding:utf-8 _*_ from contextlib import contextmanager @contextmanager def book_mark(): print('《', end="") yield print('》', end="") with book_mark(): # 核心代碼 print('且將生活一飲而盡', end="")
打印結果:
看看實際的例子,我們通常在SQLAlchemy中使用db.session.commit(),既然有commit,那就需要做一個事務的處理。因此我們需要使用try except來處理異常,如下圖所示:
一般在應用程序中,我們都有很多個db.session.commit(),那如果都要使用try來處理異常,那就太麻煩了。我們需要做的是在核心代碼之前使用try,然后在核心代碼執行完畢之后,加上except。因此這就使用到了上下文管理器,此時這個db是一個第三方類庫SQLAlchemy,那我們如何去為它新增加一個方法呢?那我們就繼承SQLAlchemy,首先導入,然后取名字:
上面我們就為SQLAlchemy新增了一個auto_commit方法,主要實現的是自動commit()和rollback();並將其變成了一個上下文管理器。
那么原始的代碼就可以變成如下的:
如下,我們在保存user的時候也使用到了db.session.commit();所以也可以改寫:
修改如下:
不是上下文管理器的類,是不能使用with語句調用的。