天天看點

Python入門之Python的單例模式和元類

一、單例模式

  單例模式(Singleton Pattern)是一種常用的軟體設計模式,該模式的主要目的是確定某一個類隻有一個執行個體存在。

  當你希望在整個系統中,某個類隻能出現一個執行個體時,單例對象就能派上用場。例如,某個伺服器程式的配置資訊存放在一個檔案中,客戶通過一個AppConfig的類來讀取配置檔案的資訊。如果在程式運作期間,有很多地方需要使用配置檔案的内容,也就是說,很多地方都需要建立AppConfig對象的執行個體,這就導緻系統中存在多個AppConfig的執行個體對象,而這樣會嚴重浪費記憶體資源,尤其實在配置檔案内容很多的情況下。事實上,類似AppConfig這樣的類,我們希望在程式運作期間隻存在一個執行個體對象。

  單例模式的要點有三個,一個是某個類隻能有一個執行個體,二是它必須自行建立這個執行個體,三是它必須自行向整個系統提供這個執行個體。

  在Python中,我們可以使用多種方法來實作單例模式:

    1. 使用子產品

    2. 使用__new__方法

    3. 使用裝飾器decorator

    4. 使用類

    5. 使用元類metaclass

  1.使用子產品

  其實,Python的子產品就是天然的單例模式。

  因為子產品在第一次導入的時候,會生成.pyc檔案,當第二次導入時,就會直接加載.pyc檔案,而不會再次執行子產品代碼。是以,我們隻需要把相關的函數和資料定義在一個子產品中,就可以獲得一個單例對象。

  如果我們真的想要一個單例類,可以考慮這樣做:

class MyClass(object):
    def foo(self):
        print('MyClass.foo')

my_class_obj = MyClass()      

    将上面的代碼儲存在檔案test1.py中,然後這樣使用:

from .test1 import my_class_obj

my _class_obj.foo()      

  2.使用__new__

  為了使類隻能出現一個執行個體,我們可以使用__new__來控制執行個體的建立過程,代碼如下:

class MyClass(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(MyClass, cls).__new__(cls, *args, **kwargs)
        return cls._instance
 
class HerClass(MyClass):
    a = 1      

  在上面的代碼中,我們将類的執行個體和一個類變量_instance關聯起來,如果cls._instance為None則建立執行個體,否則直接傳回cls._instance。

  執行情況如下:

one = HerClass()
two = HerClass()
print(one == two) #True
print(one is two)  #True
print(id(one) == id(two)) #True      

  3. 使用裝飾器

  我們知道,裝飾器decorator可以動态地修改一個類或者函數的功能。

  這裡,我們也可以使用裝飾器來裝飾某個類,使其隻能生成一個執行個體,代碼如下:

from functools import wraps

def singleton(cls):
    instance = {}    #建立字典,盛放單例

    @wraps
    def getinstance(*args,**args):

        if cls not in instance:    #cls不在字典
            instances[cls] = cls(*args, **kwargs)    
                    #以cls為key,cls(*args, **kwargs) 為值放入盛放單例的字典

        return getinstance[cls]

    return getinstance


@singleton
class MyClass(object):
    a = 1      

  在上面,我們定義要給裝飾器singleton,它傳回了一個内部函數getinstance,該函數會判斷某個類是否在字典instance中,如果不存在,則會将cls作為key, cls(*args,**kwargs)作為value存到instance中,否則,直接傳回instance[cls]。

class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()      

  執行結果:

<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>      

  看起來也沒有問題,那是因為執行速度過快。

  如果在init方法中有一些IO操作,就會發現問題了,下面我們通過time.sleep模拟

  我們在上面__init__方法中加入以下代碼

def __init__(self):
        import time
        time.sleep(1)      

  重新執行程式後,結果如下

<__main__.Singleton object at 0x034A3410>
<__main__.Singleton object at 0x034BB990>
<__main__.Singleton object at 0x034BB910>
<__main__.Singleton object at 0x034ADED0>
<__main__.Singleton object at 0x034E6BD0>
<__main__.Singleton object at 0x034E6C10>
<__main__.Singleton object at 0x034E6B90>
<__main__.Singleton object at 0x034BBA30>
<__main__.Singleton object at 0x034F6B90>
<__main__.Singleton object at 0x034E6A90>      

  問題出現了!按照以上方式建立的單例,無法支援多線程

  解決辦法:加鎖!未加鎖部分并發執行,加鎖部分串行執行,速度降低,但是保證了資料安全

import time
import threading
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

    @classmethod
    def instance(cls, *args, **kwargs):
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


def task(arg):
    obj = Singleton.instance()
    print(obj)
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)      

  列印結果如下:

<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>      

  這樣就差不多了,但是還是有一點小問題,就是當程式執行時,執行了time.sleep(20)後,下面執行個體化對象時,此時已經是單例模式了,但我們還是加了鎖,這樣不太好,再進行一些優化,把intance方法,改成下面的這樣就行:

@classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance      

  這樣,一個可以支援多線程的單例模式就完成了

import time
import threading
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


def task(arg):
    obj = Singleton.instance()
    print(obj)
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)      

  這種方式實作的單例模式,使用時會有限制,以後執行個體化必須通過 obj = Singleton.instance() 

  如果用 obj=Singleton() ,這種方式得到的不是單例

  4. 使用類 

class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls,*args,**kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instace = Singleton(*args,**kwargs)
        return Singleton._instance      

   一般情況,大家以為這樣就完成了單例模式,但是這樣當使用多線程時會存在問題

  5. 使用metaclass

  元類metaclass,可以控制類的建立過程,它主要做三件事:

    1. 攔截類的建立

    2. 修改類的定義

    3. 傳回修改後的類

  使用元類實作單例模式的代碼如下:

class Singleton(type):
    _instacne = {}
    
    def __call__(cls,*args,**kwargs):

        if cls not in cls._instance:
            cls._instances[cls] = super(Singleton, cls).__call__(*args,**kwargs)
         #以cls為key,cls(*args, **kwargs) 為值放入盛放單例的字典

        return cls._instance[cls]      
#Python2
#class MyClass(object):
    __metaclass__ = Singleton

#Python3
class MyClass(metaclass=Singleton):
    pass      

  優點:

  1. 執行個體控制

    單例模式會阻止其他對象執行個體化器自己的單例對象的副本,進而確定所有對象都通路唯一執行個體。

  2. 靈活性

    因為類控制了執行個體化過程,是以類可以靈活更改執行個體化過程。

  缺點:

  1. 開銷

    雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的執行個體,将仍然需要一些開銷。可以通過使用靜态初始化解決此問題。

  2. 可能的開發混淆

    使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用new關鍵字執行個體化對象。因為可能無法通路庫源代碼,是以應用程式開發人員可能會意外發現自己無法直接執行個體化此類。

  3. 對象生存期

    不能解決删除單個對象的問題。在提供記憶體管理的語言中(例如基于.net Framework的語言),隻有單例類能夠導緻執行個體被取消配置設定,因為它包含對該執行個體的私有引用。在某些語言中(例如C++),其他類可以删除對象執行個體,但這樣會導緻單例類中出現懸浮引用。

二、元類(metaclass)

  1. Python中一切皆對象,類也是對象。

    隻要你使用關鍵字class,Python解釋器在執行的時候會建立一個對象。

    下面的代碼段:

class MyClass(object):
    pass      

    在記憶體中建立一個對象,名字就是MyClass。這個對象(類)自身擁有建立對象(類執行個體)的能力,而這就是為什麼它是一個類的原因。但是,它本質仍然是一個對象,于是你可以對它做如下操作:

      1. 你可以将它指派給一個變量

      2. 你可以拷貝它

      3. 你可以為它增加屬性

      4. 你可以将它作為函數參數進行傳遞

class MyClass(object):
    pass

print(MyClass)    # 你可以列印這個類,因為它是對象
# <class '__main__.MyClass'>

def echo(o):
    print(o)

echo(MyClass)    # 你可以将類作為參數傳給函數
# <class '__main__.MyClass'>

MyClass.new_attribute = 'foo'     #你可以為類增加屬性
print(hasattr(MyClass,'new_attribute'))    #True
print(MyClass.new_attribute)    #foo

MyClassMirror = MyClass    #你可以将類指派給一個變量
print(MyClassMirror())
# <__main__.MyClass object at 0x00000000028CDE10>      

  2. 動态的建立類

    因為類也是對象,你可以在運作時動态的建立它們,就像其他任何對象一樣。

     首先你可以在函數中建立類,使用class關鍵字即可。

def choose_class(name):
    if name == 'foo':
        class Foo(object):
            pass
        return Foo    # 傳回的是類,不是類的執行個體
    else:
        class Bar(object):
            pass
        return Bar

MyClass = choose_class('foo')
print(MyClass)    #函數傳回的是類,不是類的執行個體
# <class '__main__.choose_class.<locals>.Foo'>

print(MyClass())    #你可以通過這個類建立類執行個體,也就是對象
# <__main__.choose_class.<locals>.Foo object at 0x00000000021E5CF8>      

    但是這還不夠動态。

    由于類也是對象,是以它們必須通過什麼東西生成才對。

    還記得内建函數type嗎?這個祖先級别的函數能夠讓你知道一個對象的類型是什麼,如下代碼:

print(type(1))     #<class 'int'>   
print(type('1'))    ##<class 'str'>
print(type(MyClass))    #<class 'type'>
print(type(MyClass()))  #<class '__main__.MyClass'>      

    type也能動态的建立類。

      type建立類的模版:

type(類名,父類的元組(針對繼承的情況,可以為空),包含屬性的字典(名稱和值))      

    比如下面的代碼:

class MyShinyClass(object):
    pass      

    可以手動像這樣建立:

MyShinyClass = type('MyShinyClass',(),{})
print(MyShinyClass)
# <class '__main__.MyShinyClass'>

print(MyShinyClass())   #  建立一個該類的執行個體
# <__main__.MyShinyClass object at 0x0000000002737D68>      

    type接受一個字典來為類定義屬性,是以:

class Foo(object):
    bar = True      

    可以翻譯為:

Foo = type('Foo',(),{'bar':True})      

    并且可以将Foo當成一個普通的類一樣使用:

class Foo(object):
    bar=True
 
print(Foo)      #<class '__main__.Foo'>
print(Foo.bar)  #True
f=Foo()        
print(f)        #<__main__.Foo object at 0x0000000001F7DE10>
print(f.bar)    #True      

    當然,你可以向這個類繼承,是以,如下的代碼:

class Foo(object):
    bar=True
 
FooChild = type('FooChild', (Foo,),{})
print(FooChild)
# <class '__main__.FooChild'>
 
print(FooChild.bar)     # bar屬性是由Foo繼承而來
# True      

     最終你會希望為你的類增加方法。隻需要定義一個有着恰當簽名的函數并将其作為屬性指派就可以了。

class Foo(object):
    bar=True
 
def echo_bar(self):
    print(self.bar)
 
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
print(hasattr(Foo, 'echo_bar'))         #False
print(hasattr(FooChild, 'echo_bar'))    #True
my_foo = FooChild()
my_foo.echo_bar()   #True      

    你可以看到,在Python中,類也是對象,你可以動态的建立類。這就是當你使用關鍵字class時Python在幕後做的事情,而這就是通過元類來實作的。

    到底什麼是元類?

    元類就是用來建立類的“東西”。元類就是類的類。

    type就是建立類對象的類。你可以通過檢查__class__屬性來看到這一點。 

    Python中所有的東西,注意,我是指所有的東西——都是對象。這包括整數、字元串、函數以及類。它們全部都是對象,而且它們都是從一個類建立而來。

age = 35
print(age.__class__)    #<class 'int'>
 
name = 'bob'
print(name.__class__)   #<class 'str'>
 
def foo():
    pass
print(foo.__class__)    #<class 'function'>
 
class Bar(object):
    pass
b = Bar()
print(b.__class__)      #<class '__main__.Bar'>      

    現在,對于任何一個__class__的__class__屬性又是什麼呢?

print(age.__class__.__class__)  #<class 'type'>
print(name.__class__.__class__) #<class 'type'>
print(foo.__class__.__class__)  #<class 'type'>
print(b.__class__.__class__)    #<class 'type'>      

    是以,元類就是建立類這種對象的東西。

    type就是Python的内建元類,當然了,你也可以建立自己的元類。

    __metaclass__屬性

    你可以在寫一個類的時候為其添加__metaclass__屬性。

class Foo(object):
    __metaclass__ = something...      

     如果你這麼做了,Python就會用元類來建立類Foo。小心點,這裡面有些技巧。你首先寫下class Foo(object),但是類對象Foo還沒有在記憶體中建立。Python會在類的定義中尋找__metaclass__屬性,如果找到了,Python就會用它來建立類Foo,如果沒有找到,就會用内建的type來建立這個類。

class Foo(Bar):
    pass      

    Python做了如下的操作:

    Foo中有__metaclass__這個屬性嗎?如果是,Python會在記憶體中通過__metaclass__建立一個名字為Foo的類對象(我說的是類對象,請緊跟我的思路)。如果Python沒有找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。如果Python在任何父類中都找不到__metaclass__,它就會在子產品層次中去尋找__metaclass__,并嘗試做同樣的操作。如果還是找不到__metaclass__,Python就會用内置的type來建立這個類對象。

    現在的問題就是,你可以在__metaclass__中放置些什麼代碼呢?

    答案就是:可以建立一個類的東西。那麼什麼可以用來建立一個類呢?type,或者任何使用到type或者子類化type的東東都可以。

​​ 參考​​