Python面經【8】- Python設計模式專題-上卷
- 一、接口
- 二、單例模式
- (1) 方法一:使用模塊
- (2) 方法二: 裝飾器實現【手撕 + 理解】(單下劃線 + 閉包 + 裝飾器 + 類方法)
- (3) 方法三:基于__new__方法【new和init 】
設計模式是一套 被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
一、接口
定義:一種特殊的類,聲明了若干方法,要求繼承該接口的類必須實現這些方法,可以通過抽象基類(Abstract Base Class)來實現。
作用:限制繼承接口的類的方法的名稱及調用方式,隱藏了類的內部實現。
1. from abc import ABCMeta, abstractmethod2. class Payment(metaclass = ABCMeta):3. @abstractmethod4. def pay(self, money):5. pass6. 7. class Payment_first(Payment):8. def pay(self, money):9. print("給%s塊錢"%money)
10.
11. pay = Payment_first()
12. pay_first = Payment_first()
13. pay_first.pay(3)
14. 結果:給3塊錢
二、單例模式
單例模式是一種常用的軟件設計模式,該模式的主要目的是確保某一個類只有一個實例存在。在系統中,某個類只能出現一個實例對象時,單例對象就能排上用場。
定義:保證一個類只能有一個實例,而客戶可以從一個眾所周知的訪問點訪問它時。
優點: 1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例。
2、避免對資源的多重占用(比如寫文件操作)。
比如,某個服務器程序的配置信息存放在一個文件中,客戶端通過一個AppConfig的類來讀取配置文件的信息。如果在程序運行期間,有很多地方都需要配置文件的內容,也就是說,很多地方都需要創建AppConfig對象的實例,這就導致系統中存在多個AppConfig的實例對象,而這樣會嚴重浪費內存,類似于AppConfig這樣的類,我們希望在程序運行期間只存在一個實例對象。
(1) 方法一:使用模塊
實現方法:將需要實現的單例功能放到一個.py文件中
實現原理:Python的模塊就是天然的單例模式,因為模塊在第一次導入時,會生成.pyc文件,當第二次導入時,就會直接加載.pyc文件,而不會再次執行模塊代碼。因此,我們只需把相關的函數和數據定義在一個模塊中,就可以獲得一個單例對象了。
1. # singleton_module.py
2. class SingletonClass:
3. def __init__(self):
4. pass
5.
6. singleton_instance = SingletonClass()
將上面的代碼保存在文件singleton_module.py中,要使用時,直接在其他文件中導入此文件中的對象,這個對象既是單例模式的對象。
1. # main.py
2. from singleton_module import singleton_instance
3.
4. # 使用singleton_instance
(2) 方法二: 裝飾器實現【手撕 + 理解】(單下劃線 + 閉包 + 裝飾器 + 類方法)
定義一個裝飾器函數,將被裝飾的類實例保存在閉包中,每次獲取實例時返回同一個實例對象。
1. 1. def Singleton(cls):2. 2. _instance = {}3. 3. def _singleton(*args, **kargs):4. 4. if cls not in _instance:#判斷該實例是否存在,存在就直接返回,不存在就創建 5. 5. _instance[cls] = cls(*args, **kargs)6. 6. return _instance[cls]7. 7. return _singleton8. 8. 9. 9. @Singleton
10. 10. class A(object):
11. 11. a = 1
12. 12. def __init__(self, x=0):
13. 13. self.x = x
14. 14.
15. 15. a1 = A(2)
16. 16. a2 = A(3)
17. 17. # 輸出:{<class '__main__.A'>: <__main__.A object at 0x7fb9af751af0>}
18. ===輸出結果===
19. <__main__.A object at 0x000001E9D1F099B0>
20. <__main__.A object at 0x000001E9D1F099B0>
- 輸出結果分析:
- 輸出顯示兩個實例的內存地址是相同的(0x000001E9D1F099B0),這證明 a1 和 a2 實際上是同一個實例的引用
- 這證實了單例模式的實現,確保了類 A 無論被實例化多少次,始終只有一個實例存在
(3) 方法三:基于__new__方法【new和init 】
我們都知道,當我們實例化一個對象時,是先執行了類的__new__方法(我們沒寫時,默認調用object.new),實例化對象;然后再執行類的__init__方法,對這個對象進行初始化,所以我們可以基于這個,實現單例模式。
個人最初常用的是重寫__new__方法的方式,但是用重寫類中的__new__方法,在多次創建時,盡管返回的都是同一個對象,但是每次執行創建對象語句時,內部的__init__方法都會被自動調用,而在某些應用場景,可能存在初始化方法只能運行只能允許運行一次的需求,這是這種單例的方式并不可取。
1. class Download(object):2. instance = None3. def __init__(self):4. print("__init__")5. def __new__(cls, *args, **kargs):6. if cls.instance is None:7. cls.instance = super().__new__(cls)8. return cls.instance9. object1 = Download()
10. object2 = Download()
11. print(object1)
12. print(object2)
運行結果,可以看到初始化方法多次執行了。
1. __init__
2. __init__
3. <__main__.Download object at 0x7f56beb4ea90>
4. <__main__.Download object at 0x7f56beb4ea90>
init方法通常用在初始化一個類實例的時候,但其實它不是實例化一個類的時候第一個被調用的方法。當使用Student(id, name)這樣的表達式來實例化一個類時,最先被調用的方法其實是new方法。
new方法接受的參數雖然也是和init一樣,但init是在類實例創建之后調用,而new方法正式創建這個類實例的方法。
New為對象分配空間,是內置的靜態方法,new在內存中為對象分配了空間也返回了對象的引用,init獲得了這個引用才初始化這個實例。
Eg:示例
一個非常簡單的單例
1. class A:
2. instance = None
3. def __new__(cls, *args, **kwargs):
4. if cls.instance is None:
5. cls.instance = super().__new__(cls)
6. return cls.instance
因為new方法是一個靜態方法(也就是在定義的時候就沒有cls參數),所以在這里要傳入一個cls參數,而且這里的new你改造過了,所以要返回爸爸的new方法。
按造這個方法改造的單例怎么new都是同一個實例,但init仍然會被執行多次,也就是創建了幾個對象就調用幾次初始化方法。所以還要對init再進行一些判斷。
1. 1. class A:2. 2. instance = None3. 3. init_flag = False # 初始化標記4. 4. 5. 5. def __new__(cls, *args, **kwargs):6. 6. if cls.instance is None:7. 7. cls.instance = super().__new__(cls)8. 8. return cls.instance9. 9.
10. 10. def __init__(self):
11. 11. if A.init_flag:
12. 12. return
13. 13. print('執行了初始化方法')
14. 14. A.init_flag = True
15. 15.
16. 16. if __name__ == '__main__':
17. 17. a = A()
18. 18. b = A()
19. 19. print(a)
20. 20. print(b)
21. ===結果如下===
22. 執行了初始化方法
23. <__main__.A object at 0x000001E9D1F096D8>
24. <__main__.A object at 0x000001E9D1F096D8>
結果分析:
- 執行了初始化
這一行表明 init 方法被執行了一次。由于在 init 方法中有一個條件檢查 (if A.init_flag:),它確保初始化代碼塊只執行一次。在第一次創建實例 a 時,init_flag 是 False,所以 init 方法的內部代碼被執行,并且 init_flag 被設置為 True。當創建第二個實例 b 時,由于 init_flag 已經是 True,init 方法內的代碼不會再次執行。 - <main.A object at 0x000001E9D1F096D8>
這兩行輸出分別是打印的 a 和 b 實例。它們具有相同的內存地址 (0x000001E9D1F096D8),這證明了 a 和 b 是同一個對象的兩個引用。這符合單例模式的預期效果,即不管創建多少次該類的實例,始終只有一個實例存在。