Python丨为什么你学不好设计模式?
課程簡介
“讀萬卷書不如走萬里路”,設計模式學不好的根本,還是在于缺少實戰。
新課《Python 設計模式基礎實戰》向大家介紹了編程語言的設計模式,著重強調大家動手實際操作。
總苦于無法將設計模式活學活用的你,不妨來學一下。
設計模式簡介
所謂“設計模式”就是一套由前人總結的代碼的設計思路。以其中最常用的“單例模式”為例,在程序中我們有一個需求,這個需求的實現有多種思路,其中一種是創建一個類并且使得該類在多次實例化時生成唯一的一個實例。這就需要設計代碼實現這個結果。大家發現這種場景下這樣設計是最合理的,我們就管這種設計思路叫做“單例模式”。
設計模式不分語言,大多數編程語言都可以實現。
一個設計模式并不像一個類或一個庫那樣能夠直接作用于我們的代碼。拿建造橋梁粗略地類比一下,一座橋是一個小功能,代碼就是磚石瓦塊鋼筋水泥,設計模式就是我們要怎么建造,圓拱橋、獨木橋、管道橋還是拉索橋。
設計模式很有用,但它要用到合適的場景中才能發揮應有的效果,否則可能出現弊大于利的情況。
通常來講設計模式分為三類:
- 創建模式,提供實例化的方法,為適合的狀況提供相應的對象創建方法。
- 結構模式,通常用來處理實體之間的關系,使得這些實體能夠更好地協同工作。
- 行為模式,用于在不同的實體建進行通信,為實體之間的通信提供更容易、更靈活的通信方法。
單例模式
所謂單例模式,也就是說任何時候我們都要確保只有一個對象實例存在。很多情況下,整個系統中只需要存在一個對象,所有的信息都從這個對象獲取,比如系統的配置對象,或者是線程池。這些場景下,就非常適合使用單例模式。
總結起來,就是說不管我們實例化一個類多少次,真正干活的對象只會生成一次并且在首次實例化時生成。
在 Python 中實現單例模式的方式有很多,下面分別舉例說明。
1)使用嵌套類實現
在定義類 A 時,在類中再定義一個嵌套類 _A。首次對類 A 進行實例化時,將類 _A 的實例賦值給類 A 的屬性 _instance ,然后給類 A 的實例定義一個 __getattr__ 方法,使得類 A 的實例調用自身屬性或方法時,都去調用類的 _instance 屬性,也就是類 _A 的實例的屬性和方法。類屬性是固定不變的,所以類 A 的實例雖然是不同的,但它們的屬性和方法都是完全一樣的。
將如下代碼寫入 singleton_1.py 文件中:
class Singleton:'''單例模式'''# 創建一個嵌套類class _A:def display(self): # 1return id(self)_instance = Nonedef __init__(self): # 2__class__._instance = __class__._instance or __class__._A()def __getattr__(self, attr): # 3return getattr(__class__._instance, attr)def __setattr__(self, attr, value): # 4object.__setattr__(__class__._instance, attr, value)if __name__ == '__main__':s1 = Singleton(); s2 = Singleton() # 5print('id(s1):', id(s1)) # 6print('id(s2):', id(s2))print('s1.display():', s1.display()) # 7print('s2.display():', s2.display())s1.name = 'James' # 8print('s1.name:', s1.name)print('s2.name:', </pre>如上所示,對代碼進行簡略說明:
- # 1 創建一個嵌套類 _A,在類內部定義一個 display 方法,該方法返回 _A 類的實例的內存地址。
- # 2 編寫 Singleton 類的實例的初始化方法。創建 Singleton 類的實例后,執行此方法。方法內部是對類的操作,為類的 _instance 屬性賦值一個 _A 的實例。第一次對 Singleton 進行實例化時會創建一個 _A 類的實例并賦值,以后不再變化。
- # 3 編寫 Singleton 類的實例獲取屬性的方法。Singleton 類內部故意不為自身的實例設置任何屬性,結果就是調用實例的屬性時最后落到此方法的頭上。方法內部獲取類屬性 _instance 的同名屬性,也就是 _A 類的實例的屬性。
- # 4 編寫 Singleton 類的實例定義屬性的方法。同樣,此方法內部調用 object.__setattr__ 方法為 Singleton._instance 也就是 _A 的實例定義屬性。
- # 5 為 Singleton 類創建兩個實例以備測試。
- # 6 打印兩個實例的內存地址,它們的結果應該是不同的。
- # 7 打印兩個實例調用 display 方法的結果,實際上調用的都是 Singleton._instance 的同名方法,結果應該是一樣的。
- # 8 其中一個實例定義 name 屬性,然后兩個實例獲取該屬性并打印,結果應該都是一樣的。
終端執行腳本,操作結果如下所示:
$ python3 singleton_1.py id(s1): 4330824208 id(s2): 4330824336 s1.display(): 4330824272 s2.display(): 4330824272 s1.name: James s2.name: James如上所示,Singleton 的實例各不相同,它們在賦值屬性和調用屬性時,結果卻是相同的。因為這些實例操作屬性時都轉移到了嵌套類 _A 的實例上。
2)使用裝飾器實現
以上代碼雖然很好地實現了單例模式,但是在真正的項目開發中這種方式卻不夠靈活,因為我們要將真正干活的類內置在單例類中,這會有些麻煩,例如刪除實例的屬性這一點就不太好實現。
下面我們使用 Python 裝飾器來實現單例模式:
首先創建一個「類裝飾器」,也就是編寫一個類,這個類作為裝飾器。這個「類裝飾器」在實例化的時候,將另一個類作為參數。類裝飾器的名字是 SingletonDeco ,其 __call__ 方法就是實例調用自身所執行的方法,我們可以把此方法的返回值定義為唯一的對象。
將如下代碼寫入 singleton_2.py 文件中:
class SingletonDeco:"""單例類裝飾器"""def __init__(self, cls): # 1print('裝飾器初始化')self._cls = clsdef instance(self): # 2try:return self._instanceexcept AttributeError:self._instance = self._cls()return self._instancedef __call__(self): # 3return self.instance()@SingletonDeco # 4 class Singleton:def display(self):return id(self)if __name__ == '__main__':s1 = Singleton() # 5s2 = Singleton()print('id(s1):', s1.display())print('id(s2):', s2.display())print('s1 is s2:', s1 is s2)對代碼進行簡單描述:
- # 1 類裝飾器的初始化方法,將被裝飾的類賦值給實例的 _cls 屬性。
- # 2 此方法用于給實例的 _instance 屬性賦值,此方法的調用權交個了 __call__ 方法,也就是說調用 SingletonDeco 類的實例時會執行 instance 方法并返回被裝飾器裝飾的類 Singleton 的實例。并且不論調用多少次,結果都是一樣的。
- # 3 類裝飾器 SingletonDeco 的實例的調用接口。
- # 4 使用類裝飾器創建 Singleton 類,創建該類時,會執行 SingletonDeco.__init__ 方法,并且將該類賦值給實例的 _cls 屬性。此時 Singleton 這個變量就指向了 SingletonDeco 這個類的實例。如果要獲取原 Singleton 類,就需要調用 Singleton 的 _cls 屬性。此外原 Singleton 類為實例提供了 display 方法返回實例的內存地址。
- # 5 調用 Singleton ,表面上看是對 Singleton 類進行實例化,實際上是調用 SingletonDeco 類的實例的 __call__ 方法。因為變量 Singleton 指向的就是 SingletonDeco 的實例。調用 __call__ 的結果就是調用 instance 方法,下一步就是對調用實例的 _cls 屬性,而這個屬性的值就是原 Singleton 類。綜上所述,這個最終的調用結果還是原 Singleton 的實例。繞這么大一圈,一切都是為了在 instance 方法中實現唯一實例。
終端執行結果如下:
$ python3 singleton_2.py 裝飾器初始化 id(s1): 4350000976 id(s2): 4350000976 s1 is s2: True以上代碼中,我們用裝飾器實現了單例模式,任何想使用單例模式的類,只需要使用 Singleton 裝飾器裝飾一下就可以使用了。
可以看到其核心工作原理其實和第一種實現方式是一致的,也是使用內置的屬性 Singleton._instance 來存儲實例的。通過使用裝飾器的模式我們將代碼解耦了,使用更加靈活。
其實這里我們也用到裝飾者模式啦,后面的章節會介紹。
3)重寫 new 方法
在對類進行實例化時,需要先調用類的 __new__ 方法創建實例,再調用實例的 __init__ 方法初始化。所以要實現單例模式,可以在類的 __new__ 方法中做文章。
首先判斷類屬性 __instance 是否存在。注意,使用 hasattr 方法時,相當于在類的外部調用類屬性,私有屬性的命令是一個下劃線加類名加屬性名。如果該屬性不存在,調用根父類 object 的 __new__ 生成一個 Singleton 類的實例并賦值給類屬性 __instance 。接下來打印類屬性 __instance 的內存地址,實際上就是類的實例的內存地址。最后返回該實例。
在對類進行實例化時,只有首次會創建類的實例,之后都是返回類的 __instance 屬性值。這樣設計就可以實現單例模式了。
將如下代碼寫入 singleton_3.py 文件:
class Singleton:def __new__(cls, *args, **kw):if not hasattr(cls, '_Singleton__instance'):cls.__instance = super().__new__(cls, *args, **kw)print('實例化時打印實例 ID:', id(cls.__instance))return cls.__instances1 = Singleton() s2 = Singleton()print('s1 is s2:', s1 is s2)其中 super().__new__ 等同于 object.__new__ 。
如果不重寫類的 __new__ 方法,則默認調用 object 的同名方法并返回,也就是每次都會創建一個新的實例。重寫的目的就是將一個實例固定到類屬性中,然后每次創建實例時都返回這個屬性值。這個方式思路簡單,代碼也很清晰。
終端執行程序結果如下:
$ python3 singleton_3.py 實例化時打印實例 ID: 4480060240 實例化時打印實例 ID: 4480060240 s1 is s2: True總結
本節實驗內容來自于《Python 設計模式基礎實戰》,主要介紹了設計模式的基本概念,并對創建型模式之一單例模式的實現進行了講解,其中涉及到三個方法,它們可以根據實際場景進行恰當地選用。所有的設計都是為了實現對某個類的實例進行唯一的限制。
后續實驗我們將講解其他模式,以及一些額外的關于“元類”的知識等。
你將學到:
點擊《Python 設計模式基礎實戰》,學習完整課程。
總結
以上是生活随笔為你收集整理的Python丨为什么你学不好设计模式?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 突破C++瓶颈,在此一举!
- 下一篇: Python + OpenCV 太好玩了