這是一個(gè)在看開(kāi)源代碼時(shí)配到了單例元類寫(xiě)法后的一個(gè)原理貼,可以幫你徹底弄清元類兔魂、以及
__new__
和__call__
烤芦,相信我,看完你絕對(duì)會(huì)有收獲析校。首先构罗,關(guān)于元類的結(jié)論說(shuō)在前頭,先有個(gè)印象——小總結(jié):
- 現(xiàn)在我們能知道為什么元類必須繼承type了:因?yàn)槲覀儗?shí)例化對(duì)象
Foo(xxx)
時(shí)調(diào)用了type.__call__
,而type.__call__
又會(huì)調(diào)用type.__new__
因此如果type子類重寫(xiě)實(shí)現(xiàn)了__new__
(返回的類實(shí)例對(duì)象的類型作控制)智玻、__call__
(對(duì)實(shí)例化的流程做控制)遂唧,則可以對(duì)類對(duì)象的類型和類屬性起到自定義的功能,而重寫(xiě)就必須繼承type=>需要元類必須繼承type- 所以按照上述的邏輯吊奢,如果定義了一個(gè)元類讓自定義類用的話
class Foo(metaclass=MyMetaClass)
盖彭,在其實(shí)例化過(guò)程中Foo()
會(huì)直接調(diào)用重寫(xiě)后的MyMetaClass.__call__
,而只要記住在MyMetaClass.__call__
中使用到return super(Singleton, cls).__call__(*args, **kwargs)
就可以把type.__call__
生成的實(shí)例返回啦页滚。所以這也是為什么<u>編寫(xiě)元類召边,一般都是繼承了type,然后根據(jù)想控制實(shí)例化流程就重寫(xiě)__call__
方法裹驰,想添加屬性就重寫(xiě)__new__
方法就行了隧熙。</u>- ★元類產(chǎn)生影響的時(shí)間點(diǎn)是在實(shí)例化的時(shí)候
看開(kāi)源代碼時(shí),看到了下面一段代碼幻林,于是對(duì)withMetaclass產(chǎn)生了好奇贞盯,經(jīng)過(guò)了解發(fā)現(xiàn)其作用是six對(duì)python2和python3使用元類兼容的寫(xiě)法。
# Python2和3兼容使用元類寫(xiě)法
class ConfigHandler(withMetaclass(Singleton)):
def __init__(self):
pass
因此沪饺,上述代碼在Python3中相當(dāng)于
# Python3元類使用寫(xiě)法
class ConfigHandler(metaclass=Singleton):
def __init__(self):
pass
那么邻悬,問(wèn)題來(lái)了,withMetaclass到底是怎么實(shí)現(xiàn)兼容的呢随闽?下面是其實(shí)現(xiàn)代碼
def withMetaclass(meta, *bases):
class MetaClass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(MetaClass, 'temporary_class', (), {})
可以看到其中出現(xiàn)了不少我們很少看到的使用方法父丰。接下來(lái)我們就仔細(xì)的學(xué)習(xí)上述寫(xiě)法為什么可以成功。
元類使用可以參考:Python3 元類(metaclass)
預(yù)置知識(shí):type和object
object 和 type的關(guān)系很像雞和蛋的關(guān)系,先有object還是先有type沒(méi)法說(shuō)蛾扇,obejct和type是共生的關(guān)系攘烛,必須同時(shí)出現(xiàn)的。
記住一點(diǎn):在Python里面镀首,所有的東西都是對(duì)象的概念坟漱,即包括類(類是type的實(shí)例對(duì)象)
最重要的兩點(diǎn)
- object類是所有類的超類(也是type類的父類)
- type是所有類的類(類型,所有類都是type的實(shí)例對(duì)象更哄,object<u>類型</u>也是type的實(shí)例對(duì)象芋齿;type 創(chuàng)建的對(duì)象擁有創(chuàng)建對(duì)象的能力(也就是類))-->是所有類的元類
此外:
- type是所有元類的父親。我們可以通過(guò)繼承type來(lái)創(chuàng)建元類(通過(guò)重寫(xiě)
type.__new__
和type.__call__
來(lái)攔截自定義類的創(chuàng)建過(guò)程)成翩。 - object是所有類的父親觅捆。
- 實(shí)例是對(duì)象關(guān)系鏈的末端,不能再被子類化和實(shí)例化麻敌。
了解到這些關(guān)鍵的點(diǎn)后栅炒,我們繼續(xù)看代碼中出現(xiàn)的一些內(nèi)容:
__new__
__new__()
是一種負(fù)責(zé)創(chuàng)建類實(shí)例的靜態(tài)方法,它無(wú)需使用 staticmethod 裝飾器修飾术羔,且該方法會(huì)優(yōu)先__init__()
初始化方法被調(diào)用赢赊。
__new__()
通常會(huì)返回該類的一個(gè)實(shí)例,但有時(shí)也可能會(huì)返回其他類的實(shí)例级历,其super().__new__(cls)
中會(huì)調(diào)用object.__init__
來(lái)Create and return a new object.
释移,因此我們可以通過(guò)改寫(xiě)子類的__new__
可以添加一些邏輯來(lái)控制實(shí)例的產(chǎn)生,然后再通過(guò)super().__new__(cls)
來(lái)生成一個(gè)instance并返回寥殖。
class demoClass:
instances_created = 0
def __new__(cls, *args, **kwargs):
# __new__(): <class '__main__.demoClass'> ('abc',) {}
print("__new__():", cls, args, kwargs)
# 1. 通過(guò)父類__new__生成一個(gè)實(shí)例: 調(diào)用父類object.__new__生成實(shí)例(Create and return a new object.)
instance = super().__new__(cls)
# 2. 自己重寫(xiě)要實(shí)現(xiàn)的邏輯
instance.number = cls.instances_created
cls.instances_created += 1
# 3. 將父類生成的實(shí)例返回
return instance
def __init__(self, attribute):
# __init__(): <__main__.demoClass object at 0x00000185A6466EB0> abc
print("__init__():", self, attribute)
self.attribute = attribute
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number, test1.instances_created)
print(test2.number, test2.instances_created)
Q:什么情況下重寫(xiě)類的__new__()
呢玩讳?答案很簡(jiǎn)單,在__init__()
不夠用的時(shí)候扛禽。
__new__()
通常會(huì)返回該類的一個(gè)實(shí)例锋边,但有時(shí)也可能會(huì)返回其他類的實(shí)例皱坛,如果發(fā)生了這種情況编曼,則會(huì)跳過(guò)對(duì) __init__()
方法的調(diào)用。而在某些情況下(比如需要修改不可變類實(shí)例(Python 的某些內(nèi)置類型)的創(chuàng)建行為)剩辟,利用這一點(diǎn)會(huì)事半功倍掐场。比如:http://c.biancheng.net/view/5484.html,對(duì) Python 不可變的內(nèi)置類型(如 int贩猎、str熊户、float 等)進(jìn)行了子類化,這是因?yàn)橐坏﹦?chuàng)建了這樣不可變的對(duì)象實(shí)例吭服,就無(wú)法在__init__()
方法中對(duì)其進(jìn)行修改嚷堡。
注:由于 __new__()
不限于返回同一個(gè)類的實(shí)例,所以很容易被濫用,不負(fù)責(zé)任地使用這種方法可能會(huì)對(duì)代碼有害蝌戒,所以要謹(jǐn)慎使用串塑。
MetaClass元類
承接上文
__new__
,Python中大量使用__new__()
方法且合理的地方北苟,就是 MetaClass 元類桩匪。MetaClass元類,并不是某一個(gè)類的名字友鼻,它是一個(gè)概念傻昙,是一種Python的思想。當(dāng)然其本質(zhì)也是一個(gè)類彩扔,但和普通類的用法不同妆档,它可以對(duì)類內(nèi)部的定義(包括類屬性和類方法)進(jìn)行動(dòng)態(tài)的修改〗杞埽可以這么說(shuō)过吻,使用元類的主要目的就是為了實(shí)現(xiàn)在創(chuàng)建類時(shí),能夠動(dòng)態(tài)地改變類中定義的屬性或者方法蔗衡。其可以將創(chuàng)建對(duì)象的過(guò)程攔截下來(lái)纤虽,從而對(duì)這個(gè)對(duì)象進(jìn)行自定義(這個(gè)需要類繼承type,與前文繼承object的做區(qū)別)绞惦。
明確一點(diǎn):元類可以理解成是自定義類繼承的父類(從兼容寫(xiě)法中也能看出)逼纸,但元類的特點(diǎn)是不會(huì)出現(xiàn)在自定義類的繼承關(guān)系(
__mro__
)之中
舉個(gè)例子,根據(jù)實(shí)際場(chǎng)景的需要济蝉,我們要為多個(gè)類添加一個(gè) name 屬性和一個(gè) say() 方法杰刽。顯然有多種方法可以實(shí)現(xiàn),但其中一種方法就是使用 MetaClass 元類王滤。
# 定義一個(gè)元類贺嫂,繼承type。因?yàn)橹挥欣^承type才能通過(guò)重寫(xiě)__new__來(lái)攔截創(chuàng)建過(guò)程
# ▲注意雁乡,繼承type后__new__能拿到的參數(shù)信息跟不繼承type的有天壤之別第喳,原因繼續(xù)看下去
class FirstMetaClass(type):
# cls代表元類的類: FirstMetaClass
# name代表自定義類的類名: CLanguage
# bases代表被動(dòng)態(tài)修改的類的所有父類
# attr代表被動(dòng)態(tài)修改的類的所有屬性、方法組成的字典
def __new__(cls, name, bases, attrs):
# 動(dòng)態(tài)為該類添加一個(gè)name屬性
attrs['name'] = "C語(yǔ)言中文網(wǎng)"
attrs['say'] = lambda self: print("調(diào)用 say() 實(shí)例方法")
return super().__new__(cls,name,bases,attrs)
# 定義類時(shí)踱稍,指定元類
class CLanguage(object, metaclass=FirstMetaClass):
pass
clangs = CLanguage()
print(clangs.name)
clangs.say()
可以看到曲饱,在創(chuàng)建類時(shí),通過(guò)在標(biāo)注父類的同時(shí)指定元類(格式為metaclass=元類名
)珠月,則當(dāng) Python 解釋器在創(chuàng)建該類實(shí)例時(shí)扩淀,FirstMetaClass(type)
元類中的__new__
方法就會(huì)被調(diào)用,其中bases和attrs能拿到自定義類的參數(shù)啤挎,從而實(shí)現(xiàn)動(dòng)態(tài)修改類屬性或者類方法的目的驻谆。
元類和父類的區(qū)別:
在定義子類的時(shí)候,我們有兩個(gè)選擇:①是傳需要繼承的父類;②自定義的元類胜臊。
- 父類是子類的模板氛谜,子類的功能是跟父類緊耦合的,子類和父類一般是一一對(duì)應(yīng)的
- 元類是子類的修飾器区端,可以為該子類和其他子類都添加自定義功能值漫,并且不在繼承關(guān)系中(
Class.__mro__
查看),子類和元類是一對(duì)多的關(guān)系织盼。元類并不是特地為某個(gè)子類服務(wù)的
class TestMeta3(type):
def __new__(cls, name, bases, attrs):
print(cls) # 當(dāng)前類
print("name", name) # 如果是通過(guò)metaclass觸發(fā)的杨何,此處為調(diào)用metaclass的類的類型
print("bases", bases) # 如果是通過(guò)metaclass觸發(fā)的,此處為調(diào)用metaclass的類的父類
print("attrs", attrs) # 如果是通過(guò)metaclass觸發(fā)的沥邻,此處為調(diào)用metaclass的類的屬性
return type.__new__(cls, name, bases, attrs)
class Pa3:
pass
# python3中可以直接通過(guò)metaclass關(guān)鍵字參數(shù)來(lái)指定類的元類
class Eg3(Pa3, metaclass=TestMeta3):
@classmethod
def get(self):
kkk = []
kkk.append(self.__skiless__)
return kkk
def acc2(self):
return 'a2'
"""
輸出
<class '__main__.TestMeta3'>
name Eg3
bases (<class '__main__.Pa3'>,)
attrs {'__module__': '__main__', '__qualname__': 'Eg3', 'get': <classmethod object at 0x00000263511C6FA0>, 'acc2': <function Eg3.acc2 at 0x00000263511C5310>}
"""
在定義的時(shí)候危虱,發(fā)現(xiàn)竟然有輸出。因?yàn)槎x的時(shí)候唐全,python解釋器會(huì)在當(dāng)前類中查找metaclass[3]埃跷,如果找到了,就使用該metaclass創(chuàng)建Eg3類邮利。所以打印出來(lái)的name弥雹、bases、attrs都和Eg3有關(guān)延届。
with_metaclass
由于python2和python3中元類使用方法的不同剪勿,我們需要使用一種兼容的方式[1],如下所示:
def withMetaclass(meta, *bases):
"""Create a base class with a metaclass."""
class MetaClass(meta):
# 如果刪除__new__方庭,則類.__mro__中能看到meta類
def __new__(cls, name, this_bases, d):
# 因?yàn)閙eta是類厕吉,所以這邊是在調(diào)用meta的__call__()⌒的睿▲bases头朱,d為ConfigHandler的父類和屬性
return meta(name, bases, d)
# 返回一個(gè)新類型, type.__new__()要求第一個(gè)必須是type的子類
return type.__new__(MetaClass, 'temporary_class', (), {})
# 下面兩句話等價(jià),withMetaclass是為了兼容python2和python3,2中沒(méi)有metaclass關(guān)鍵字
# 其在創(chuàng)建實(shí)例時(shí)龄减,__new__方法會(huì)被MetaClass攔截(其實(shí)就是子類沒(méi)定義__new__项钮,走了父類的__new__)
class ConfigHandler(withMetaclass(Singleton))
# python3寫(xiě)法
class ConfigHandler(metaclass=Singleton)
# 因?yàn)镃onfigHandler相當(dāng)于繼承了 type.__new__返回的類MetaClass,所以在c = ConfigHandler()實(shí)例化的時(shí)候欺殿,會(huì)觸發(fā)Metaclass的__new__然后調(diào)用meta.__call__從而返回一個(gè)對(duì)象
with_metaclass
返回的臨時(shí)類中寄纵,本身無(wú)任何屬性鳖敷,但包含了元類和基類的所有信息脖苏,并在下一步定義類時(shí)將所有信息解包出來(lái)[1]。
見(jiàn):★Python 元類及with_metaclass
type
動(dòng)態(tài)創(chuàng)建類
- type() 函數(shù)屬于 Python 內(nèi)置函數(shù)定踱,通常用來(lái)查看某個(gè)變量的具體類型棍潘。
type(obj)
- 其實(shí),type() 函數(shù)還有一個(gè)更高級(jí)的用法,即創(chuàng)建一個(gè)自定義類型(也就是創(chuàng)建一個(gè)類)亦歉。
-
type(name, bases, dict)
:其中 name 表示類的名稱恤浪;bases 表示一個(gè)元組,其中存儲(chǔ)的是該類的父類肴楷;dict 表示一個(gè)字典水由,用于表示類內(nèi)定義的屬性或者方法。
-
實(shí)際上type(name, bases, dict)
是調(diào)用了type類的type.__init__(cls, what, bases=None, dict=None)
方法赛蔫,創(chuàng)建了一個(gè)type的實(shí)例(類類型就是一個(gè)type實(shí)例)砂客,類型是<class 'type'>
<class 'type'>是所有類型的類型。<class 'object'>也是所有對(duì)象的超類(除了它自己呵恢,包括type)
▲. 此外type還有type.__new__(*args, **kwargs)
鞠值,其作用是Create and return a new object.
,可以寫(xiě)成type.__new__(ClassTpye, name, base, dicts)
渗钉,但ClassType必須是type的子類彤恶。會(huì)返回一個(gè)跟ClassType有關(guān)系的新類型
通過(guò)元類創(chuàng)建單例類
現(xiàn)在讓我們正式看,我在開(kāi)源代碼里看到的內(nèi)容:
# 注意這邊繼承了type, 所以下面的__call__是重寫(xiě)type的__call__鳄橘,即創(chuàng)建實(shí)例的方法
class Singleton(type):
"""
Singleton Metaclass
"""
_inst = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._inst:
cls._inst[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._inst[cls]
def withMetaclass(meta, *bases):
"""Create a base class with a metaclass."""
# 這需要一點(diǎn)解釋:基本思想是為一個(gè)級(jí)別的類實(shí)例化創(chuàng)建一個(gè)虛擬元類声离,用實(shí)際的元類替換它自己。
# KeyPoint1. 繼承meta類
class MetaClass(meta):
# 實(shí)際上下面的__new__不影響
def __new__(cls, name, this_bases, d):
# cls為withMetaclass; name為使用者的類型; this_bases為使用者的父類們; d為使用者的屬性
return meta(name, bases, d)
# KeyPoint2. type.__new__創(chuàng)建一個(gè)名稱叫temporary_class瘫怜,類型為MetaClass的類
# ▲注意type.__new__中的類類型必須是type的子類
return type.__new__(MetaClass, 'temporary_class', (), {})
class ConfigHandler(withMetaclass(Singleton)):
def __init__(self):
print("__init__")
pass
@LazyProperty
def serverHost(self):
return os.environ.get("HOST", setting.HOST)
# res=withMetaclass(Singleton)的類型為<class 'util.six.withMetaclass.<locals>.MetaClass'>
# 將其傳給ConfigHandler作為父類抵恋,在定義 ConfigHandler 時(shí)會(huì)觸發(fā)MetaClass.__new__, 于是調(diào)用meta(name, bases, d),此處的meta為Singleton宝磨,而name為ConfigHandler類, bases為空, d為ConfigHandler的屬性和方法弧关。
# print(type(ConfigHandler)) ==> <class 'util.singleton.Singleton'>
# 因此 c = ConfigHandler() ==> Singleton的__call__方法,
當(dāng)c = ConfigHandler()時(shí)會(huì)因?yàn)閠ype.__new__(MetaClass, 'temporary_class', (), {})去找MetaClass的__call__進(jìn)行調(diào)用唤锉,MetaClass沒(méi)有__call__則找到了其父類meta(Singleton)的__call__
注:類也是對(duì)象世囊,是元類的對(duì)象,即我們實(shí)例化一個(gè)類時(shí)窿祥,調(diào)用其元類的__call__(cls, *args, **kwargs)
方法進(jìn)行創(chuàng)建對(duì)象株憾。
__call__
一個(gè)非常特殊的實(shí)例方法,即
__call__()
晒衩。該方法的功能是在類中重載了對(duì)象的 () 運(yùn)算符嗤瞎,使得類實(shí)例對(duì)象可以像調(diào)用普通函數(shù)那樣,以“對(duì)象名()”的形式使用听系。
實(shí)際上贝奇,如果不重寫(xiě)__call__
的話,Class.__call__(*args, **kwargs)
還承擔(dān)著產(chǎn)生類實(shí)例的功能(會(huì)調(diào)用父類(可以通過(guò)Class.__class__
來(lái)查看父類)的type.__call__
其會(huì)返回一個(gè)實(shí)例)
案例一:
# 默認(rèn)繼承的是object, 而不是type
class Meta:
def __init__(self, name):
print("init")
self.name = name
def __call__(self, *args, **kwargs):
print("call")
# 當(dāng)沒(méi)有重寫(xiě)__call__時(shí)靠胜,無(wú)論是顯式的調(diào)用__call__掉瞳,還是通過(guò)()運(yùn)算符調(diào)用毕源,都會(huì)調(diào)用type.__call__返回一個(gè)實(shí)例
res = Meta.__call__("asd")
print(res, type(res))
res = Meta("asd")
print(res, type(res))
"""
init
<__main__.Meta object at 0x0000014FB5115EE0> <class '__main__.Meta'>
init
<__main__.Meta object at 0x0000014FB5115A90> <class '__main__.Meta'>
上述兩種都能創(chuàng)建對(duì)象
"""
# 當(dāng)重寫(xiě)__call__以后, __call__()返回實(shí)例的效果就失效了==>因?yàn)樯鲜龃a沒(méi)有return
# 此時(shí) Meta()與Meta.__call__()不再等價(jià)
"""
call
None <class 'NoneType'>
init
<__main__.Meta object at 0x0000016CC2745EE0> <class '__main__.Meta'>
"""
Q:我們?cè)趯?shí)例化一個(gè)對(duì)象的時(shí)候f = Foo(1, y=2)
,可以發(fā)現(xiàn)在__init__()
中并沒(méi)有返回實(shí)例陕习,但調(diào)用Foo(1, y=2)
確實(shí)返回了一個(gè)對(duì)象霎褐,而且,__init__
預(yù)期一個(gè)self
參數(shù)该镣,但是當(dāng)我們調(diào)用Foo(1, y=2)
時(shí)這里并沒(méi)有這個(gè)參數(shù)冻璃。那么類實(shí)例化的過(guò)程到底是怎么樣的呢?
A:構(gòu)造順序——理解python的類實(shí)例化
首先明確一點(diǎn)损合,Python中的類也是對(duì)象俱饿!類、函數(shù)塌忽、方法以及實(shí)例都是對(duì)象——類類型是type的對(duì)象拍埠,并且無(wú)論何時(shí)你將一對(duì)括號(hào)放在它們的名字后面時(shí),就會(huì)調(diào)用type.__call__()
方法土居。為什么呢枣购?因?yàn)閠ype是類型的父類
>>> Foo.__class__
<class 'type'>
所以Foo
是類型type
的一個(gè)對(duì)象,并且調(diào)用type類的__call__(self, *args, **kwargs)
返回一個(gè)Foo
類的對(duì)象擦耀。讓我們看下type
中的__call__
方法是什么樣的棉圈。這個(gè)方法相當(dāng)?shù)膹?fù)雜,但是我們將其C代碼轉(zhuǎn)成Python代碼眷蜓,并嘗試盡量簡(jiǎn)化它分瘾,結(jié)果如下。
class type(object):
# 這邊的obj_type跟cls一樣
def __call__(obj_type, *args, **kwargs):
# 通過(guò)__new__創(chuàng)建一個(gè)空的類實(shí)例吁系,如果obj_type沒(méi)有__new__則使用type.__new__
obj = obj_type.__new__(*args, **kwargs)
# 進(jìn)行類型檢查
if obj is not None and issubclass(obj, obj_type):
# 對(duì)類進(jìn)行__init__初始化
obj.__init__(*args, **kwargs)
# 返回類實(shí)例
return obj
可見(jiàn)__new__
方法為對(duì)象分配了內(nèi)存空間德召,構(gòu)建它為一個(gè)“空"對(duì)象然后__init__
方法被調(diào)用來(lái)初始化它。
那我們定義了一個(gè)具體類來(lái)講解這個(gè)過(guò)程汽纤。首先明確一點(diǎn):Foo相對(duì)于產(chǎn)生了一個(gè)type實(shí)例化對(duì)象
class Foo(object):
def __init__(self, x, y=0):
self.x = x
self.y = y
獲得實(shí)例化對(duì)象Foo(*args, **kwargs)
也可以看作是type對(duì)象()
即調(diào)用了type中()運(yùn)算符的觸發(fā)的函數(shù)type.__call__
從而創(chuàng)建一個(gè)Foo的實(shí)例
- 至于
type.__call__
發(fā)生了什么就是上面抽象代碼中介紹的那般上岗,調(diào)用type.__new__(Foo, *args, **kwargs)
然后返回一個(gè)對(duì)象實(shí)例obj。 -
obj
隨后通過(guò)調(diào)用obj.__init__(*args, **kwargs)
被初始化蕴坪。 -
obj
被type.__call__
中返回肴掷。
▲注意:Foo.__call__
重載的是foo對(duì)象
的()運(yùn)算符,而Foo()
實(shí)例化foo對(duì)象背传,則執(zhí)行的是type對(duì)象
的()運(yùn)算符呆瞻。
小總結(jié):
- 現(xiàn)在我們能知道為什么元類必須繼承type了:因?yàn)槲覀儗?shí)例化對(duì)象
Foo(xxx)
時(shí)調(diào)用了type.__call__
,而type.__call__
又會(huì)調(diào)用type.__new__
因此如果type子類重寫(xiě)實(shí)現(xiàn)了__new__
(返回的類實(shí)例對(duì)象的類型作控制)、__call__
(對(duì)實(shí)例化的流程做控制)径玖,則可以對(duì)類對(duì)象的類型和類屬性起到自定義的功能,而重寫(xiě)就必須繼承type=>需要元類必須繼承type - 所以按照上述的邏輯挺狰,如果定義了一個(gè)元類讓自定義類用的話
class Foo(metaclass=MyMetaClass)
明郭,在其實(shí)例化過(guò)程中Foo()
會(huì)直接調(diào)用重寫(xiě)后的MyMetaClass.__call__
瞳购,而只要記住在MyMetaClass.__call__
中使用到return super(Singleton, cls).__call__(*args, **kwargs)
就可以把type.__call__
生成的實(shí)例返回啦话侄。所以這也是為什么<u>編寫(xiě)元類,一般都是繼承了type,然后根據(jù)想控制實(shí)例化流程就重寫(xiě)__call__
方法滴劲,想添加屬性就重寫(xiě)__new__
方法就行了攻晒。</u> - ★元類產(chǎn)生影響的時(shí)間點(diǎn)是在實(shí)例化的時(shí)候
注意點(diǎn):元類繼承了type,所以實(shí)例化元類是在產(chǎn)生一個(gè)類類型班挖,就要以type創(chuàng)建類類型的參數(shù)去產(chǎn)生鲁捏。而元類的使用一般都是自定義類class MyClass(metaclass=元類)
,然后實(shí)例化自定義類MyClass(xxx)
總結(jié):看完上述知識(shí)點(diǎn)后萧芙,我們能知道為什么withclass能起到metaclass的作用(類的__mro__
中不出現(xiàn)指定的元類)了:
- 首先分析流程:
return type.__new__(Metaclass)
返回了一個(gè)類型供自定義類繼承给梅,由于MetaClass繼承的是真正的元類(元類都繼承type),所以在自定義類實(shí)例化的時(shí)候會(huì)被Metaclass的__new__
方法攔截双揪,在MetaClass.__new__
里return了一個(gè)自定義實(shí)例破喻,并把對(duì)象加入到了Singleton字典中了。 - 其次講解為什么MetaClass中沒(méi)有MetaClass:因?yàn)楦鶕?jù)
__new__
知識(shí)點(diǎn)中講到的盟榴,__new__
控制了實(shí)例產(chǎn)生曹质,return type.__new__(Metaclass)
中創(chuàng)建了Metaclass
,但其在__new__
中返回的并不是MetaClass擎场,因此__mro__
中不會(huì)出現(xiàn)Metaclass
- 最后還要講講Singleton中的執(zhí)行邏輯:
class Singleton(type):
_inst = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._inst:
# super(Singleton, cls).__call__調(diào)用的是type.__call__(自定義類類名name, 自定義類父類bases, 自定義類屬性attrs)
cls._inst[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._inst[cls]