Python魔法模塊之contextlib


      今天在逛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語句調用的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM