Python中的閉包與裝飾器
關於Python中的閉包與裝飾器的知識筆者之前總結過一篇文章:Python裝飾器的調用過程
實際上,裝飾器是Python中的的一個語法糖,使用@裝飾器裝飾的函數會將被裝飾的函數作為參數傳入裝飾器函數中,然后在裝飾器函數里面做一些統一的定制化的處理。
也就是說,我們可以使用裝飾器在被裝飾函數執行之前或之后實現一些統一的自定制的邏輯。
比如說,筆者在實際開發中重構代碼時遇到了這樣的問題:新函數與舊函數的入參數跟出參一模一樣,只不過由於使用的工具的版本不同需要新函數中做一下特殊的處理,為了代碼簡潔筆者沒有在調用舊函數的地方直接將舊的函數名修改成新函數名,而是做了一個裝飾器裝飾上層函數,使用反射的方式將舊函數替換為新函數,下面是實現的代碼:
import sys # 配置文件 老版本就不變了為True is_old_flag = False # 入參必須一樣。。。 def no_print(msg): # 新邏輯 print("no_print...") def my_print(msg): # 舊邏輯 print(">>> ", msg) # 裝飾器中動態修改函數 def wrapper(func): def inner(*args, **kwargs): if hasattr(sys.modules[__name__], "my_print"): if not is_old_flag: # setattr..... setattr(sys.modules[__name__], "my_print", no_print) func(*args, **kwargs) return inner @wrapper def t1(): my_print("xxxxx") t1()
當然實際中我們可以選擇各種方式實現自己的需求,筆者只是拋磚引玉大概說一下裝飾器的用途。
在Golang中實現Python裝飾器同樣的效果
Golang中也可以實現與裝飾器同樣的效果,簡單的思路是我們可以把函數當作參數傳入另外一個函數中,然后再在最外層的函數里面靈活的執行函數即可:
最近寫業務代碼使用golang的xorm框架,在執行事務時會使用到Transaction方法,我們可以看一下它這個方法的實現:
package xorm // Transaction Execute sql wrapped in a transaction(abbr as tx), tx will automatic commit if no errors occurred func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interface{}, error) {
// 在f執行之前做一些處理
session := engine.NewSession() defer session.Close() if err := session.Begin(); err != nil { return nil, err } result, err := f(session) // 這里執行f if err != nil { return nil, err }
// 在f執行之后做一些處理 if err := session.Commit(); err != nil { return nil, err } return result, nil }
實際的例子
舉一個實際的例子,比如說如果我們想計算一個函數的執行時間,可以這樣寫:
package main import ( "fmt" "time" ) // 傳入的函數聲明一個type type decFunc func(string) string // 計算執行時間 func CalTimeCost(f decFunc, input1 string) time.Duration { start := time.Now() f(input1) end := time.Now() ret := end.Sub(start) return ret } func MyTest(s1 string) string { time.Sleep(time.Second * 2) return s1 + "_666" } func main() { timeCost := CalTimeCost(MyTest, "whw") fmt.Println("函數的執行時間: ", timeCost) }
結果如下:
MyTest的執行時間為: 2.003631894
雖然代碼很簡單,而且將一個函數作為參數傳入另外一個函數中的做法實際中不常用,但是這也算是一種技術積累把。
Golang中使用類似裝飾器功能的業務代碼以及使用map存放操作的函數簡化if判斷 *****
簡化的版本看這里:

package task import ( "fmt" "testing" ) // 常量 const ( WX string = "1" OneHundred string = "2" VIP string = "3" ) // 統一處理的函數 var funcMap = map[string]func(string) (bool, error){ WX: userBindWX, OneHundred: userLevel100, VIP: userVIP, } func TestHigh(t *testing.T) { userID := "user_id_test" taskIDList := []string{"1", "2", "3", "4", "5", "6"} var retLst []UserTask // 同一個用戶在不同的表中根據 userID 找到對應的記錄,判斷他有沒有完成對應的任務 // 1、是否綁定微信 2、是否已經達到100級別 3、是否已經完成VIP充值 // 完成了上述的3個任務 就把它們的Status設置為2 // 其他任務的Status初始化為1 // TODO 使用方便的辦法統一處理 for _, taskID := range taskIDList { var currStatus = 1 if currFunc, ok := funcMap[taskID]; ok { ok, err := currFunc(userID) if err != nil { panic(err) } if ok { currStatus = 2 } } currUserTask := NewUserTask(userID, taskID, currStatus) retLst = append(retLst, currUserTask) } fmt.Print("retLst: ", retLst) /* taskID為1 2 3的狀態為2,剩下的都為1 retLst: [{user_id_test 1 2} {user_id_test 2 2} {user_id_test 3 2} {user_id_test 4 1} {user_id_test 5 1} {user_id_test 6 1}] */ } // 判斷用戶是否綁定微信 func userBindWX(userID string) (bool, error) { // TODO 業務代碼省略 return true, nil } // 判斷用戶是否達到100級 func userLevel100(userID string) (bool, error) { // TODO 業務代碼省略 return true, nil } // 判斷用戶是否充值了VIP func userVIP(userID string) (bool, error) { // TODO 業務代碼省略 return true, nil }
這里是原版的代碼:
// model包中定義的常量 // 每日任務中的成長任務 const ( BindMaster int = 8 BindWX int = 9 HasWithdraw int = 10 HasInvite int = 11 ) // 任務是否完成的狀態 const ( NotFinish int = 1 // 未完成 FinishButNotReward int = 2 // 已完成未領取 Rewarded int = 3 // 已領取 )
~~~
var funcMap = map[int]func(string) (bool, error){ model.BindMaster: userInvitee, model.BindWX: userBindWX, model.HasWithdraw: userWithdraw, model.HasInvite: userInvitor, } // 根據userValue找到user_task的配置然后在Redis中創建 func GenUserTaskDataInRedisByUserIdAndUserValue(userId, userValue string) ([]UserTaskModel, error) { userTaskModelList := make([]UserTaskModel, 0) tasks := GetTaskList(userValue) for _, taskItem := range tasks { userTask := NewUserTask(&taskItem, userId) // 適配"成長任務":8: 綁定師父 9: 綁定微信 10: 完成第一筆提現 11: 邀請好友
// 下面的4個函數的模式一模一樣,完全可以統一處理,這樣省去了4個if判斷~ if currFunc, ok := funcMap[userTask.TaskId]; ok { status, err := getTaskStatusDecorator(currFunc, userId, taskItem.TaskId) if err != nil { return nil, err } userTask.Status = status } userTaskModelList = append(userTaskModelList, *userTask) } // HMSet (~~ 業務代碼 可忽略) err := BatchCreateUserTaskForSingleUserInRedis(userTaskModelList) if err != nil { return nil, err } return userTaskModelList, nil } // 判斷用戶是否綁定師傅 實際上是找這個用戶是否被邀請過 func userInvitee(userID string) (bool, error) { _, has, err := invite.GetByInvitee(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } // 被邀請過證明完成過任務了 if has { return true, nil } return false, nil } // 判斷用戶是否綁定微信 func userBindWX(userID string) (bool, error) { userModel, has, err := users.GetUserByUserId(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } // 如果有這個用戶 判斷wx_id是否為空 if has { if userModel.WxId != "" { return true, nil } } return false, nil } // 判斷用戶是否提過現 func userWithdraw(userID string) (bool, error) { withMoneyRecord := WithdrawRecordModel{UserId: userID} exist, err := withMoneyRecord.Exist() if err != nil { return false, errnum.New(errnum.DbError, err) } if exist { return true, nil } return false, nil } // 判斷用戶之前是否邀請過人 func userInvitor(userID string) (bool, error) { uLst, err := invite.QueryByInviter(userID) if err != nil { return false, errnum.New(errnum.DbError, err) } if len(*uLst) > 0 { return true, nil } return false, nil } // 統一處理判斷狀態 3個參數:判斷是否完成過任務的函數judgeFunc、userID、taskID func getTaskStatusDecorator(judgeFunc func(userID string) (bool, error), userID string, taskID int) (int, error) { // 從user_game_task表中找記錄 userGameTask, has, err := GetUserGameTaskByUserIdTaskIdNoSession(userID, taskID) if err != nil { return 0, errnum.New(errnum.DbError, err) } // 如果有記錄 就用這里的 if has { return userGameTask.Status, nil } // 如果沒有記錄需要在user_game_task中創建一條記錄 status需要根據實際情況來判斷 userGameTaskModel := UserGameTaskModel{ UserGameTaskId: uuid.RandStringRunesTime(4), UserId: userID, GameTaskId: taskID, } // 判斷用戶實際上有沒有完成對應傳過來判斷的函數中的任務 completeTask, err := judgeFunc(userID) if err != nil { return 0, err } if completeTask { // 完成了任務還需要判斷一下有沒有領獎 typeCoinHistory := TypeCoinHistoryModel{UserID: userID, ItemID: taskID} exist, err := typeCoinHistory.Exist() if err != nil { return 0, errnum.New(errnum.DbError, err) } if exist { userGameTaskModel.Status = model.Rewarded } else { userGameTaskModel.Status = model.FinishButNotReward } } else { userGameTaskModel.Status = model.NotFinish } // 創建user_game_task記錄 err = userGameTaskModel.CreateNoSession() if err != nil { return 0, errnum.New(errnum.DbError, err) } return userGameTaskModel.Status, nil }
~~~