元類的理解

回憶一下以前type的介紹python

class Foo(object):
  
  
    def __init__(self,name):
        self.name = name
  
  
f = Foo("lxj")

print( type(f)) # 輸出:<class '__main__.Foo'>     表示,obj 對象由Foo類建立
print( type(Foo)) # 輸出:<type 'type'>              表示,Foo類對象由 type 類建立

  能夠得出f對象是Foo類的一個實例Foo類是 type 類的一個實例,即:Foo類 是經過type類建立的實例。type就是一個類的類,咱們也稱爲元類git

 

因爲元類是一個類的類,因此它被用來構造類(就像一個類用於構造對象同樣)。可是等一下,咱們不是建立一個具備標準類定義的類嗎?固然,可是Python在底下作了什麼:github

當它看到一個類定義時,Python會執行它來收集字典中的屬性(包括方法)。
當類定義結束時,Python肯定類的元類。咱們稱之爲Meta
最終,Python執行Meta(name,base,dct),其中:
Meta是元類,因此這個調用正在實例化它。
name是新建立的類的名稱
base:類的基類的一個元組
dct將屬性名稱映射到對象,列出全部類的屬性
咱們如何肯定一個類的元類?簡單地說,若是一個類或它的一個基類有一個__metaclass__屬性,它就被看成元類。不然,類型是元類。django

當咱們定義一個函數

class MyKlass(object):
  foo = 2

  在這個例子中,沒有__metaclass__屬性,因此就用type代替,建立過程像下面這樣debug

MyKlass = type(name, bases, dct)

  若是metaclass被定義調試

class MyMeta():
    pass
class MyKlass(object,metaclass=MyMeta):  # python3寫法

    foo = 2

  建立過程就像這樣對象

MyKlass = MyMeta(name, bases, dct)

  

__new__ :當你想要控制一個新的對象的建立(在咱們的例子中是類)blog

__init__ :應該在建立新對象後控制新對象的初始化時執行。內存

MyKlass = MyMeta.__new__(MyMeta, name, bases, dct)
MyMeta.__init__(MyKlass, name, bases, dct)

 看個例子 

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print('-----------------------------------')
        print ("Allocating memory for class", name)
        print ("meta>>",meta)   #經過程序meta:<class '__main__.MyMeta'>
        print (bases)
        print (dct)


        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print ('-----------------------------------')
        print ("Initializing class", name)   
        print ("cls>>",cls)    #cls:<class '__main__.MyKlass'>
        print (bases)
        print (dct)

        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(object,metaclass=MyMeta):
    # __metaclass__ = MyMeta

    def foo(self, param):
        pass

    barattr = 2

  

 咱們使用pycharm的debug模式來看下程序的運行順序:

一、解釋器從上到下執行,class MyMeat - __new__ - __init__ - class MyKclass -def foo -barattr  個人理解是依次得到函數或則屬性的內存地址

二、檢測到有metaclass字段,運行__new__ ,__init__

有調試結果得出來,當有metaclass字段時,當解釋器編譯完全部代碼時,會回過頭再執行metaclass指向的類中的__new__和__init__方法

用元類實現一個單例模式:

當咱們建立一個類時,每次實例化都是一個新的內存地址,看代碼

class Spam(object):
    def __init__(self):
        print("creating Spam")

s = Spam()
a =Spam()
print(s is a )
#結果爲False

 再看用元類實現單例模式:

class Singleton(type):
    def __init__(cls,*args,**kwargs): #cls類
        print("Singleton __init__")
        print(cls,*args,*kwargs)
        cls.__instance = None
        super().__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs):

        print("Singleton __call__")
        print(cls)
        if cls.__instance is None: #判斷實例是否被建立
            cls.__instance = super().__call__(*args,**kwargs)
            print(cls.__instance)
            return cls.__instance
        else:
            return cls.__instance

class Spam(metaclass=Singleton):
    def __init__(self,name):
        print("creating Spam name is ",name)
# sp= Singleton(1)
s = Spam('lxj')
a =Spam('sx')
print(s is a )

結果:
Singleton __init__
<class '__main__.Spam'> Spam () {'__module__': '__main__', '__init__': <function Spam.__init__ at 0x7fc0096ff8c8>, '__qualname__': 'Spam'}
Singleton __call__
<class '__main__.Spam'>
creating Spam name is  lxj
<__main__.Spam object at 0x7fc00970dcf8>
Singleton __call__
<class '__main__.Spam'>
True

  

一、首先在實例化前,由於有metaclas存在,咱們調用了__init__函數(參考上面的解釋),把cls._instance置爲None(即還未實例化),這裏cls爲Spam,爲何不用__new__方法,由於__new__方法中cls爲Singleton。上面的知識點咱們也知道__init__方法是用在控制新對象的初始化

二、在結果中咱們看到,在咱們實例化過程當中,調用的是__call__,每實例話一次,就調用一次__call__方法,在call方法中對實例化進行控制

三、結果中咱們看到a和s爲指向同一內存地址

 

 

在學到django時再仔細深刻https://github.com/inglesp/Metaclasses

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python

相關文章
相關標籤/搜索