元類在很多編程語(yǔ)言中都有這樣的概念用爪,我們都知道,類可以創(chuàng)建對(duì)象胁镐,類本身也是對(duì)象偎血,既然是對(duì)象,那么它肯定也是被創(chuàng)造出來(lái)的希停,元類就專門用來(lái)創(chuàng)造類對(duì)象烁巫,于是,這就給我們提供了一種操縱或者監(jiān)聽類的能力宠能。
平時(shí)我們創(chuàng)建一個(gè)類亚隙,使用的是這種方式:
class MyClass(object):
def method(self):
return 1
instance1 = MyClass()
print(instance1.method())
如果把類也看成一個(gè)對(duì)象,利用元類违崇,我們這樣創(chuàng)建一個(gè)類:
def method(self):
return 1
klass = type('MyClass', (object,), {'method': method})
instance1 = klass()
print(instance1.method())
如果從寫法上看阿弃,沒(méi)什么太大的區(qū)別,MetaClass真正有用的地方是羞延,我們可以自定義元類渣淳,接下來(lái)我們看一段代碼:
class RevealingMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
print(mcs, "__new__ called")
return super().__new__(mcs, name, bases, namespace)
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
print(metacls, "__prepare__ called")
return super().__prepare__(name, bases, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
print(cls, "__init__ called")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
print(cls, "__call__ called")
return super().__call__(*args, **kwargs)
上邊的代碼可以算是固定寫法了,這其中比較重要的概念是這4個(gè)方法的調(diào)用時(shí)機(jī):
-
__new__
當(dāng)某個(gè)類被創(chuàng)建的時(shí)候會(huì)調(diào)用 -
__prepare__
在創(chuàng)建類的時(shí)候伴箩,可以傳入額外的字典class Klass(metaclass=Metaclass, extra="value"):
,這個(gè)方法就是用來(lái)創(chuàng)建接收dict的入愧,所以這個(gè)方法會(huì)在__new__
前邊調(diào)用 -
__init__
這個(gè)方法算是對(duì)__new__
的一些補(bǔ)充 -
__call__
這個(gè)方法會(huì)在類被創(chuàng)建的時(shí)候調(diào)用
我們就利用上邊代碼創(chuàng)建出來(lái)的元類,創(chuàng)建一個(gè)類:
class RevealingMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
print(mcs, "__new__ called")
return super().__new__(mcs, name, bases, namespace)
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
print(metacls, "__prepare__ called")
return super().__prepare__(name, bases, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
print(cls, "__init__ called")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
print(cls, "__call__ called")
return super().__call__(*args, **kwargs)
class RevealingClass(metaclass=RevealingMeta):
def __new__(cls, *args, **kwargs):
print(cls, "__new__ called")
return super().__new__(cls)
def __init__(self):
print(self, "__init__ called")
super().__init__()
如果這時(shí)候直接執(zhí)行代碼,會(huì)打印出:
<class '__main__.RevealingMeta'> __prepare__ called
<class '__main__.RevealingMeta'> __new__ called
<class '__main__.RevealingClass'> __init__ called
<class '__main__.RevealingClass'> __call__ called
這說(shuō)明棺蛛,只要?jiǎng)?chuàng)建了類就會(huì)調(diào)用上邊的方法怔蚌,這些方法的調(diào)用跟實(shí)例的創(chuàng)建沒(méi)有關(guān)系,接下來(lái)執(zhí)行:
instance12 = RevealingClass()
在上邊打印的后邊旁赊,會(huì)打印出:
<class '__main__.RevealingClass'> __new__ called
<__main__.RevealingClass object at 0x104032048> __init__ called
這就是元類的基本用法了桦踊,它一般會(huì)用在很多的framework中,但也容易出錯(cuò)终畅,我們可以為某個(gè)類隨意指定元類籍胯,如果該元類沒(méi)有實(shí)現(xiàn)這些方法,就有可能會(huì)出現(xiàn)崩潰离福。