標籤:

Python 設計模式初探

作者:研究路上

原文連接:jianshu.com/p/2ab60b84f

查看更多的專業文章,請移步至「人工智慧LeadAI」公眾號,查看更多的課程信息和產品信息,請移步至全新打造的官網:www.leadai.org.

正文共4097個字,4張圖,預計閱讀時間11分鐘。

本文章是在閱讀精通Python設計模式(中文版)(book.douban.com/subject),以及閱讀 Mask R-CNN 第三方Tensorflow代碼的基礎上記錄得到。

豆瓣上似乎對該書的評價不高,這裡僅以此書為基礎,試圖理解Python中常見的設計模式,並有效看懂相關代碼。

01

工廠模式

1.1 實際需求

假設我希望對多種 CNN model (例如 "AlexNet" 和 "VGGNet")的分類性能進行測試,因此我可能需要去實現不同的 CNN model的代碼,並在主函數分別進行調用測試。

1.2 直接方式

我首先採用 Tensorflow(之後簡寫為TF) 編寫多種類型的 CNN model 對應的 class。例如, "AlexNet" 對應的class是 AlexCNN,"VGGNet" 對應的class是 VGGCNN。然後在 main 函數中直接實例化不同類別的class,然後依次進行不同 網路 instance 的"訓練" 和 "測試",最後得到每種CNN model的分類性能。

下面給出這種方式的代碼示例:

# 多個類別的網路class 定義class AlexCNN:n def __init__(*args, **kwargs):n # do initializationclass VGGCNN:n def __init__(*args, **kwargs):n # do initializationif __name__ == "__main__":n A = AlexCNN()n B = VGGCNN() # training and testing, etcn

注意,這裡的方式是將各個網路 class 的實例化放到主函數中進行。這樣操作會使得內部的class暴露給外部。

我們希望採用一種新的方式,直接提出需求(給出網路類別),然後某函數返回給我一個實例化好了的類,我也不需要在main函數中去顯式調用類別實例化了。

1.3 工廠設計模式

在工廠設計模式中,客戶(我)只希望按照自己的要求(CNN model 類別)獲得相應的商品(對應類別的 instance),而不關心商品是如何生成的。該設計模式背後的思想是希望簡化對象的創建。

下面給出工廠設計模式的代碼示例:

# 多個類別的網路class 定義class AlexCNN:n def __init__(*args, **kwargs):n # do initializationclass VGGCNN:n def __init__(*args, **kwargs):n # do initializationdef CNN_factory(name, *args, **kwargs):n if str(name) == "AlexNet": return AlexCNN(args, kwargs) if str(name) == "VGGNet": return VGGCNN(args, kwargs)if __name__ == "__main__":n A = CNN_factory("AlexNet")n B = CNN_factory("VGGNet") # training and testing, etcn

與直接在main函數中實例化的方式相比,通過基於函數的實現,更易於追蹤創建了哪些對象。通過將創建對象的代碼和使用對象的代碼解耦,工廠設計模式能夠降低應用維護的複雜度。

02

裝飾器模式

2.1 實際需求

一個簡單的例子

假設現在有多個函數,有的函數是遞歸的,我希望對這些函數進行微修改,然後列印輸出系統在執行函數時,進行的調用過程以及消耗的時間

以下面兩個函數為例,

# 非遞歸的import timedef snooze(seconds):n time.sleep(seconds)# 遞歸的def factorial(n):n return 1 if n < 2 else n * factorial(n-1)n

2.2 直接方式

2.2.1 直接修改每個函數內部的實現

當函數很少的時候,可以直接對函數內部進行修改,使其達到列印輸出的功能

如下這樣改造,

import timedef snooze(seconds, needPrint=False)n start_time = time.time()n time.sleep(seconds)n end_time = time.time()n # 新添加的 if needPrint:n print("[%f s] snooze(%s) -> None".format(end_time - start_time, str(seconds)))def factorial(n, needPrint=False):n start_time = time.time()n result = 1 if n < 2 else n * factorial(n-1)n end_time = time.time() if needPrint:n print("[%f s] snooze(%s) -> %s".format(end_time - start_time, str(n), str(result))) return resultn

這種方式存在的問題是,原本的函數內部結構被修改得七零八落,列印輸出的功能與函數本身的計算功能耦合在了一起,如果要改變列印方式,勢必需要重新修改函數內部的列印輸出的實現方式。增加了後續的維護成本。

2.2.2 傳入列印函數

既然在函數內部直接編寫列印代碼不太合適,那就將列印函數以參數的形式傳入。當需要修改列印輸出模式時,直接修改列印函數的形式即可。相當於將列印函數與計算函數解耦。

如下這樣改造,

import timedef printFunc1(elapsedTime, funcName, inputParam, outputParam): nprint("[%s] %s(%s) -> %s".format(elapsedTime, funcName, str(inputParam), str(outputParam)))ndef printFunc2(*args, **kwargs): npassdef snooze(seconds, printFunc): nstart_time = time.time() ntime.sleep(seconds) nend_time = time.time() nprintFunc(elapsedTime="[%f] s".format(end_time - start_time), funcName="snooze", inputParam=str(seconds), outputParam=str(None))ndef factorial(n, printFunc): nstart_time = time.time() nresult = 1 if n < 2 else n * factorial(n-1) nend_time = time.time() nprintFunc(elapsedTime="[%f] s".format(end_time - start_time), funcName="factorial", inputParam=str(n), outputParam=str(result)) nreturn resultn

這種方式相比上一種方法,耦合性得以降低,但如果現在我想調用

不帶計時作用的函數,可能又得重新修改這個函數了。那能不能想一種方法,技能獲得計時效果,又可以保證原來的函數不受到破壞。

2.3 裝飾器設計模式

2.3.1 什麼是裝飾器設計模式

在已有函數的基礎上,我們希望對一個對象添加額外的功能。那麼有以下幾種方法:

  • 如果合理,直接將功能添加到對象所屬的類(例如,添加一個新的方法)
  • 使用組合
  • 使用繼承

而裝飾器模式則提供了第四種方法,以支持動態地(runtime,運行時)擴展一個對象的功能。裝飾器(decorator)模式能夠以透明的方式(不會影響其它對象)動態地將功能添加到一個對象中。

2.3.2 Python中的裝飾器

很多編程語言中都使用子類化(繼承)來實現裝飾器模式。而Python中內置了裝飾器特性。一個Python裝飾器就是對Python語法的一個特定改變,用於擴展一個類,方法或函數的行為,而無需使用繼承。

例如,假如有個名為decorate的裝飾器:

# 用decorate裝飾器來對以下的函數進行裝飾@decoratedef target():n print( "running target()")# 實際上,上述寫法是一種語法糖,它等價於def target():n print( "running target()" )ntarget = decorate(target)n

可以看到,裝飾器是可以調用的對象,其參數是另一個函數(即被裝飾的函數)。裝飾器可能會處理被裝飾的函數,然後把它返回,或者將其替換成另一個函數或可調用對象。

2.3.3 如何解決上述問題

直接上代碼,

import timedef clock(func):n def clocked(*args):n t0 = time.perf_counter()n result = func(*args)n elapsed = time.perf_counter() - t0n name = func.__name__n arg_str = , .join(repr(arg) for arg in args)n print([%0.8fs] %s(%s) -> %r % (elapsed, name, arg_str, result)) return result return clocked@clockdef snooze(seconds):n time.sleep(seconds)@clockdef factorial(n):n return 1 if n < 2 else n * factorial(n-1)# 測試一下if __name__ == "__main__":n print(* * 40, Calling snooze(.123))n snooze(.123)n print(* * 40, Calling factorial(6))n print(6! =, factorial(6))n

可以看到,裝飾器clock以函數為輸入,輸出的也是函數,但是中間加了額外的代碼邏輯。

推薦閱讀:

Python中 __init__的通俗解釋?
Python GUI教程(一):在PyQt5中創建第一個GUI圖形用戶界面
python的list是數組的結構還是鏈表的結構?
喵哥的Django學習筆記1:安裝
python根據BM25實現文本檢索

TAG:Python |