1. 程式人生 > 實用技巧 >六、單例模式

六、單例模式

擴充套件:__call__

只要它含有__call__屬性,這個物件就是callable的,物件後就可以加()進行呼叫

# 含有__call__屬性的,是可呼叫的,能夠加括號
# 反之沒有__call__屬性,就是不可呼叫的,不能加括號
print(dir(lambda x, y: x+y)) # ['__annotations__', '__call__'.....]
print(callable(lambda x,y:x+y)) # True
print((lambda x,y:x+y)(1,2)) # 3

class MyClass(object):
    pass

if __name__
== '__main__': a = MyClass() print(a()) # TypeError: 'MyClass' object is not callable

一、__init__() 和 __new__()

__init__():建立物件、然後分配記憶體

__new__():初始化物件(並沒有建立物件)

執行順序是:先執行new(建立物件),再執行init(初始化)

new中:繼承父類,MyClass類建立物件instance並返回,返回的物件會傳給init中的self,執行初始化。

class MyClass(object):
    def __init__
(self): print("init is running...") # new會先建立物件、然後分配記憶體 def __new__(cls, *args, **kwargs): pass print("new is running...") instance = super().__new__(cls) # 建立物件(物件是由類建立的,所以是cls return instance if __name__ == '__main__': a = MyClass() print(type(a))
""" new is running... init is running... <class '__main__.MyClass'> """

二、多例模式

建立多少個物件,就分配多少個記憶體;每個物件獨享一份記憶體

class MyClass(object):
    pass

if __name__ == '__main__':
    a = MyClass()
    b = MyClass()
    print(id(a))   
    print(id(b))   

三、單例模式

不管建立多少個物件,永遠用同一個物件,物件只建立一次。

省記憶體,提高效能

1.使用__new__實現單例模式

不管建立多少物件,最終都是同一個物件,只分配一個記憶體地址

class MyClass(object):
    obj = None

    #如果物件已經建立 就直接把建立的物件返回
    #如果物件未建立,建立物件,並把物件返回
    def __new__(cls, *args, **kwargs):
         if cls.obj is None:
             cls.obj = super().__new__(cls)
         return cls.obj 

2. 使用裝飾器實現單例模式

def singleton(cls):
    isinstance = {}

    def wrapper(*args, **kwargs):
        if not isinstance:
            isinstance[cls] = cls(*args, **kwargs)
        return isinstance[cls]

    return wrapper

@singleton
class MyClass(object):
    pass

if __name__ == '__main__':
    a = MyClass()
    b = MyClass()
    print(id(a))
    print(id(b))

3.使用import實現單例模式

deno_2.py

class MyClass(object):
    pass

myclass = MyClass()
from demo_2 import myclass
print(id(myclass)) # 2785165023752

from demo_2 import myclass
print(id(myclass)) # 2785165023752

不管import幾次,只會在第一次分配記憶體

注意:第一次匯入模組時,python直譯器會建立pyc檔案;以後再匯入時,不會執行匯入程式碼而是直接使用pyc檔案

4.用指定的類方法實現單例模式

class MyClass(object):
    __instance  = None

    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def singleton(cls ,*args, **kwargs):
        if not cls.__instance:
            cls.__instance = cls(*args, **kwargs)
        return cls.__instance

if __name__ == '__main__':
    a = MyClass.singleton()
    b = MyClass.singleton()
    c = MyClass()
    d = MyClass()
    print(id(a))   # 2514584144968
    print(id(b))   # 2514584144968
    print(id(c))   # 2514584145032
    print(id(d))   # 2514584145096

擴充套件:python物件建立的記憶體什麼時候釋放?

python的垃圾回收機制(如上面的類建立的物件放在一個main()函式中,呼叫main函式並執行完畢後,函式內部的物件會全部銷燬)

# 實現單例 與 非單例共存
class MyClass(object):
    __instance  = None

    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def singleton(cls ,*args, **kwargs):
        if not cls.__instance:
            cls.__instance = cls(*args, **kwargs)
        return cls.__instance

def main():
    a = MyClass.singleton()
    b = MyClass.singleton()
    c = MyClass()
    d = MyClass()
    print(id(a))  # 1577432513672
    print(id(b))  # 1577432513672
    print(id(c))  # 1577432513736
    print(id(d))  # 1577432513800

if __name__ == '__main__':    
    main()

要儘可能少的定義全域性變數,如果不寫del 變局變數,全域性變數是不會被銷燬的,除非程式結束。

四、建立執行緒安全的單例模式

執行緒不安全:雖然使用了單例模式,但是在多執行緒的情況下,單例模式“失效”了

# 執行緒不安全
# 多併發時,多執行緒造成的非單例
import threading
import time

class MyClass(object):
    __instance  = None

    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def singleton(cls ,*args, **kwargs):
        if not cls.__instance:
            cls.__instance = cls(*args, **kwargs)
        return cls.__instance

def my_obj():
    # 列印 檢視建立記憶體
    print(MyClass.singleton())
    return MyClass.singleton()

if __name__ == '__main__':
    # 建立10個執行緒
    tasks = [threading.Thread(target=my_obj) for i in range(10)]
    # 同時啟動10個執行緒
    for task in tasks:
        task.start()

"""
<__main__.MyClass object at 0x000002991B431608>
<__main__.MyClass object at 0x000002991B431708>
<__main__.MyClass object at 0x000002991B431588>
<__main__.MyClass object at 0x000002991B431508>
<__main__.MyClass object at 0x000002991B4316C8>
<__main__.MyClass object at 0x000002991B431748>
<__main__.MyClass object at 0x000002991B431688>
<__main__.MyClass object at 0x000002991B4315C8>
<__main__.MyClass object at 0x000002991B431548>
"""

如何建立執行緒安全的單例模式呢?-----建立鎖,加鎖

原理:將建立物件條件的程式碼段加鎖,鎖住後,同時只能有一個執行緒在建立物件,建立物件結束後,會自動釋放鎖。第二次再建立的時候,instance非none,就實現了單例模式了

import threading
import time

class MyClass(object):
    __instance  = None

    # 建立鎖
    __lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        pass

    @classmethod
    def singleton(cls ,*args, **kwargs):
        with cls.__lock:  # with 語句段 結束後會自動釋放鎖(unlock)
            if not cls.__instance:
                cls.__instance = cls(*args, **kwargs)
            return cls.__instance

def my_obj():
    # 列印 檢視建立記憶體
    print(MyClass.singleton())
    return MyClass.singleton()

if __name__ == '__main__':
    # 建立10個執行緒
    tasks = [threading.Thread(target=my_obj) for i in range(10)]
    # 同時啟動10個執行緒
    for task in tasks:
        task.start()
        
"""
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
<__main__.MyClass object at 0x000001F931AC1448>
"""