Python的6種方式實現單例模式
阿新 • • 發佈:2020-04-23
單例模式是一個軟體的設計模式,為了保證一個類,無論呼叫多少次產生的例項物件,都是指向同一個記憶體地址,僅僅只有一個例項(只有一個物件)。
實現單例模式的手段有很多種,但總的原則是保證一個類只要例項化一個物件,下一次再例項的時候就直接返回這個物件,不再做例項化的操作。所以這裡面的關鍵一點就是,如何判斷這個類是否例項化過一個物件。
這裡介紹兩類方式:
- 一類是通過模組匯入的方式;
- 一類是通過魔法方法判斷的方式;
# 基本原理: - 第一類通過模組匯入的方式,借用了模組匯入時的底層原理實現。 - 當一個模組(py檔案)被匯入時,首先會執行這個模組的程式碼,然後將這個模組的名稱空間載入到記憶體。 - 當這個模組第二次再被匯入時,不會再執行該檔案,而是直接在記憶體中找。 - 於是,如果第一次匯入模組,執行檔案原始碼時例項化了一個類,那再次匯入的時候,就不會再例項化。 - 第二類主要是基於類和元類實現,在'物件'的魔法方法中判斷是否已經例項化過一個物件 - 這類方式,根據實現的手法不同,又分為不同的方法,如: - 通過類的繫結方法;通過元類;通過類下的__new__;通過裝飾器(函式裝飾器,類裝飾器)實現等。
下面分別介紹這幾種不同的實現方式,僅供參考實現思路,不做具體需求。
通過模組匯入
# cls_singleton.py
class Foo(object):
pass
instance = Foo()
# test.py
import cls_singleton
obj1 = cls_singleton.instance
obj2 = cls_singleton.instance
print(obj1 is obj2)
# 原理:模組第二次匯入從記憶體找的機制
通過類的繫結方法
class Student: _instance = None # 記錄例項化物件 def __init__(self, name, age): self.name = name self.age = age @classmethod def get_singleton(cls, *args, **kwargs): if not cls._instance: cls._instance = cls(*args, **kwargs) return cls._instance stu1 = Student.get_singleton('jack', 18) stu2 = Student.get_singleton('jack', 18) print(stu1 is stu2) print(stu1.__dict__, stu2.__dict__) # 原理:類的繫結方法是第二種例項化物件的方式, # 第一次例項化的物件儲存成類的資料屬性 _instance, # 第二次再例項化時,在get_singleton中判斷已經有了例項物件,直接返回類的資料屬性 _instance
通過魔法方法__new__
class Student: _instance = None def __init__(self, name, age): self.name = name self.age = age def __new__(cls, *args, **kwargs): # if cls._instance: # return cls._instance # 有例項則直接返回 # else: # cls._instance = super().__new__(cls) # 沒有例項則new一個並儲存 # return cls._instance # 這個返回是給是給init,再例項化一次,也沒有關係 if not cls._instance: # 這是簡化的寫法,上面註釋的寫法更容易提現判斷思路 cls._instance = super().__new__(cls) return cls._instance stu1 = Student('jack', 18) stu2 = Student('jack', 18) print(stu1 is stu2) print(stu1.__dict__, stu2.__dict__) # 原理:和方法2類似,將判斷的實現方式,從類的繫結方法中轉移到類的__new__中 # 歸根結底都是 判斷類有沒有例項,有則直接返回,無則例項化並儲存到_instance中。
通過元類
class Mymeta(type):
def __init__(cls, name, bases, dic):
super().__init__(name, bases, dic)
cls._instance = None # 將記錄類的例項物件的資料屬性放在元類中自動定義了
def __call__(cls, *args, **kwargs): # 此call會在類被呼叫(即例項化時觸發)
if cls._instance: # 判斷類有沒有例項化物件
return cls._instance
else: # 沒有例項化物件時,控制類造空物件並初始化
obj = cls.__new__(cls, *args, **kwargs)
obj.__init__(*args, **kwargs)
cls._instance = obj # 儲存物件,下一次再例項化可以直接返回而不用再造物件
return obj
class Student(metaclass=Mymeta):
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
# 原理:類定義時會呼叫元類下的__init__,類呼叫(例項化物件)時會觸發元類下的__call__方法
# 類定義時,給類新增一個空的資料屬性,
# 第一次例項化時,例項化之後就將這個物件賦值給類的資料屬性;第二次再例項化時,直接返回類的這個資料屬性
# 和方式3的不同之處1:類的這個資料屬性是放在元類中自動定義的,而不是在類中顯示的定義的。
# 和方式3的不同之處2:類呼叫時觸發元類__call__方法判斷是否有例項化物件,而不是在類的繫結方法中判斷
函式裝飾器
def singleton(cls):
_instance_dict = {} # 採用字典,可以裝飾多個類,控制多個類實現單例模式
def inner(*args, **kwargs):
if cls not in _instance_dict:
_instance_dict[cls] = cls(*args, **kwargs)
return _instance_dict.get(cls)
return inner
@singleton
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# def __new__(cls, *args, **kwargs): # 將方法3的這部分程式碼搬到了函式裝飾器中
# if not cls._instance:
# cls._instance = super().__new__(cls)
# return cls._instan
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
類裝飾器
class SingleTon:
_instance_dict = {}
def __init__(self, cls_name):
self.cls_name = cls_name
def __call__(self, *args, **kwargs):
if self.cls_name not in SingleTon._instance_dict:
SingleTon._instance_dict[self.cls_name] = self.cls_name(*args, **kwargs)
return SingleTon._instance_dict.get(self.cls_name)
@SingleTon # 這個語法糖相當於Student = SingleTon(Student),即Student是SingleTon的例項物件
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
# 原理:在函式裝飾器的思路上,將裝飾器封裝成類。
# 程式執行到與語法糖時,會例項化一個Student物件,這個物件是SingleTon的物件。
# 後面使用的Student本質上使用的是SingleTon的物件。
# 所以使用Student('jack', 18)來例項化物件,其實是在呼叫SingleTon的物件,會觸發其__call__的執行
# 所以就在__call__中,判斷Student類有沒有例項物件了。