單例模式
這是一種設計模式
- 設計模式是前任工作的總結和提煉,通常,被人們廣泛流傳的設計模式都是針對某一特定問題的成熟的解決方案
- 使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性
單例設計模式
- 目的:讓某一個類創建的實例對象,在整個應用程序中只有唯一的一個實例對象而且該對象易於外界訪問,從而方便對實例個數的控制並節約系統資源
- 每一次執行 類名() 返回的對象,內存地址是相同的
單例設計模式的應用場景
- 音樂播放器對象
- 回收站對象
- 打印機對象
- .....
為什么要單例模式?
- 提問:如何保證一個類只有一個實例並且這個實例易於被訪問呢?
- 不使用單例模式:定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止實例化多個對象
- 單例模式的出現:類自己負責只能創建一個實例對象,可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法
__new__ 方法
使用 類名() 創建對象時,Python 的解釋器首先會調用 __new__ 方法為對象分配內存空間
class PoloBlog: def __new__(cls, *args, **kwargs): print("分配內存地址啦") def __init__(self): print("初始化對象...") blog = PoloBlog() print(blog) # 輸出結果 分配內存地址啦 None
哎,為什么打印對象是 None,而且沒有調用到 __init__ 方法呢??下面講解!
內置的靜態方法
__new__ 是一個由 object 基類提供的內置的靜態方法
__new__ 主要作用
- 在內存中為實例對象分配空間
- 返回對象的引用給 Python 解釋器
Python 的解釋器獲得對象的引用后,將對象的引用作為第一個參數,傳遞給 __init__ 方法
重寫 __new__ 方法
- 重寫的代碼是固定的
- 重寫 __new__ 方法一定要在最后 return super().__new__(cls)
- 如果不 return(像上面代碼栗子一樣),Python 的解釋器得不到分配了空間的對象引用,就不會調用對象的初始化方法(__init__)
- 重點:__new__ 是一個靜態方法,在調用時需要主動傳遞 cls 參數
class PoloBlog: def __new__(cls, *args, **kwargs): # 1、自動調用 __new__ print("分配內存地址啦") # 2、為對象分配空間得到的引用賦值給 instance instance = super().__new__(cls) print(id(instance)) # 3、返回對象引用給 Python 解釋器 return instance def __init__(self): print("初始化對象...") print(id(self)) blog = PoloBlog() # 輸出結果 分配內存地址啦 4363809888 初始化對象... 4363809888
可以看到打印的兩個內存地址是同一個哦:證明 __new__ 分配的對象引用的確傳給了 __init__ 方法的 self 參數
__new__ 實現單例模式
class PoloBlog: def __new__(cls, *args, **kwargs): print("分配內存地址啦") instance = super().__new__(cls) return instance def __init__(self): print("初始化對象...") blog = PoloBlog() blog1 = PoloBlog() print(id(blog)) print(id(blog1)) # 輸出結果 4449363040 4449361984
很明顯,兩個對象各有自己的內存地址;單純的重寫 __new__ 方法並不能實現單例模式
__new__ 實現單例模式的邏輯
單例:在整個應用程序中只有唯一的一個實例對象
- 定義一個類屬性,來保存單例對象的引用
- 重寫 __new__ 方法
- 如果類屬性 is None,則調用父類方法分配內存空間,並賦值給類屬性
- 如果類屬性已有對象引用,則直接返回
單例模式的代碼實現
# 單例模式 class PoloBlog: instance = None def __new__(cls, *args, **kwargs): # 1、判斷類屬性是否為 None if cls.instance is None: # 2、為空,調用父類方法,給對象分配內存空間,並賦值給類屬性 cls.instance = super().__new__(cls) # 3、如果不為空,則直接返回類屬性保存的對象引用 return cls.instance def __init__(self): pass blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() print(id(blog), id(blog1), id(blog2)) # 輸出結果 4336982096 4336982096 4336982096
可以看到創建的三個實例對象其實都是同一個,這就是單例模式!
初始化工作僅執行一次
在每次使用類名()創建對象時,Python 的解釋器都會自動調用兩個方法
- __new__ 分配空間
- __init__ 對象初始化
上面所說的單例模式,是針對 __new__ 方法進行重寫的,創建多個實例對象都會得到同一個實例對象
但是:初始化方法還是會被多次調用
class PoloBlog: instance = None def __new__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__new__(cls) return cls.instance def __init__(self): print("yep") blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() # 輸出結果 yep yep yep
假設想讓初始化動作只執行一次呢?
其也很簡單,和單例模式的解決思路差不多
- 定義一個類屬性標記是否執行過初始化動作,初始值為 False
- 在 __init__ 方法中,判斷類屬性,如果 False,則執行初始化動作,然后設置為 True
- 如果 True 則直接跳過不執行
# 單例模式 class PoloBlog: instance = None init_flag = None def __new__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__new__(cls) return cls.instance def __init__(self): # 1、判斷是否為 True,因為是實例方法,所以調用類屬性要通過類對象 if PoloBlog.init_flag: # 2、如果 True,直接跳過不執行后續初始化動作 return # 3、如果 False,則執行 print("初始化動作") # 4、修改 init_flag PoloBlog.init_flag = True blog = PoloBlog() blog1 = PoloBlog() blog2 = PoloBlog() # 輸出結果 初始化動作