1. 程式人生 > >Python的6種方式實現單例模式

Python的6種方式實現單例模式

單例模式是一個軟體的設計模式,為了保證一個類,無論呼叫多少次產生的例項物件,都是指向同一個記憶體地址,僅僅只有一個例項(只有一個物件)。

實現單例模式的手段有很多種,但總的原則是保證一個類只要例項化一個物件,下一次再例項的時候就直接返回這個物件,不再做例項化的操作。所以這裡面的關鍵一點就是,如何判斷這個類是否例項化過一個物件。

這裡介紹兩類方式:

  • 一類是通過模組匯入的方式;
  • 一類是通過魔法方法判斷的方式;
# 基本原理:
- 第一類通過模組匯入的方式,借用了模組匯入時的底層原理實現。
- 當一個模組(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類有沒有例項物件了。