熟悉 Golang 的同學(xué)都知道,Golang 里面有一個關(guān)鍵詞叫做defer,它可以實(shí)現(xiàn)延遲調(diào)用。
實(shí)際上在 Python 里面也有相關(guān)的語法,那就是contextlib.ExitStack。
我們來看這樣一個場景:
我有一個函數(shù)parse,它的作用是從 Redis 中持續(xù)讀入數(shù)據(jù),并寫入到MongoDB 中。示例代碼如下:
- import json
- import redis
- import pymongo
- client = redis.Redis()
- handler = pymongo.MongoClient().test.data
- def parse():
- data = client.lpop('test')
- if not data:
- return
- handler.insert_one(json.loads(data))
但現(xiàn)在我想增加一個需求,當(dāng)Redis 讀取結(jié)束或者讀取數(shù)據(jù)報錯的時候,能把當(dāng)前的時間也寫入到MongoDB 中。
那么代碼可能變成下面這樣:
- import json
- import redis
- import datetime
- import pymongo
- client = redis.Redis()
- handler = pymongo.MongoClient().test.data
- def parse():
- while True:
- try:
- data = client.lpop('test')
- if not data:
- handler.insert_one({'finished': True, 'ts': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
- return
- handler.insert_one(json.loads(data))
- except Exception:
- handler.insert_one({'finished': True, 'ts': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'})
可以看到,代碼變得很難看了。
現(xiàn)在,我們可以使用延遲調(diào)用來讓代碼變得更好看。
要實(shí)現(xiàn)這個目的,就可以開始使用ExitStack了。它可以注冊多個回調(diào)函數(shù),在退出上下文縮進(jìn)時執(zhí)行。
我們先來看一個簡單的例子:
- import contextlib
- def callback_1():
- print('我是第一個回調(diào)函數(shù)')
- def callback_2(x):
- print(f'我是第二個回調(diào)函數(shù),傳入?yún)?shù):{x}')
- with contextlib.ExitStack() as stack:
- stack.callback(callback_1)
- stack.callback(callback_2, 100)
- print(12345)
- print('xxxx')
- print('退出縮進(jìn)')
運(yùn)行效果如下圖所示:
可以看出以下特點(diǎn):
- 被添加的回調(diào)函數(shù)進(jìn)入了一個棧,所以后添加的回調(diào)函數(shù)先調(diào)用
- 回調(diào)函數(shù)會在結(jié)束縮進(jìn)的時候被調(diào)用
現(xiàn)在我們來人工構(gòu)造一個異常:
可以看到,即使縮進(jìn)里面出現(xiàn)了報錯,回調(diào)函數(shù)仍然可以正常運(yùn)行。等所有回調(diào)函數(shù)運(yùn)行完成以后,Python 才會退出。
基于以上特點(diǎn),我們就可以來重構(gòu)最開始的代碼了:
- import json
- import redis
- import datetime
- import pymongo
- import contextlib
- client = redis.Redis()
- handler = pymongo.MongoClient().test.data
- def add_ts():
- handler.insert_one({'finished': True, 'ts': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')})
- def parse():
- with contextlib.ExitStack() as stack:
- stack.callback(add_ts)
- while True:
- data = client.lpop('test')
- if not data:
- return
- handler.insert_one(json.loads(data))
無論是正常運(yùn)行結(jié)束還是運(yùn)行過程中報錯,add_ts函數(shù)都會正常運(yùn)行,確保始終增加一條日期數(shù)據(jù)。
原文鏈接:https://mp.weixin.qq.com/s/fjbOA0YFW1jrsthtNem5aQ