印象中,是在建立單例模式時知道可以用到元類(metaclass),但始終對其瞭解的不是很透徹,很多人也都說元類是Python中較難理解的概念之一,於是找來幾本書,希望可以找到答案,本文以Python3為例。
本文參考:
《人人都懂設計模式》
《Python Cookbook》
《 流暢的Python》
先來簡單介紹下:元類(metaclass)是一個類,你也可以理解為類的類,因為Python中的類是在執行時動態建立的,那麼通過元類便可以控制類屬性和類例項的建立過程。
來看看用元類實現的單例模式:
class Singleton(type):
"""
單例模式
"""
def __init__(cls, *args, **kwargs):
cls.__instance = None
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
class Test(metaclass=Singleton):
def __init__(self):
pass
a = Test()
b = Test()
print(id(a))
print(id(b))
具體的實現是:建立類時顯式的指定類的metaclass,而自定義的metaclass繼承type,並重新實現__call__
方法。
於是,有了兩個問題:
- 為什麼自定義的metaclass繼承type?
因為,在Python中,type是預設的metaclass(內建元類),Python允許我們自定義metaclass,自定義的metaclass必須繼承自type,也就是:元類從type類繼承了構建類的能力。
我們通常用type來獲取物件所屬的類,就像這樣:
In [10]: a = 10
In [11]: type(a)
Out[11]: int
然而,type還是一個類,你可以通過type來新建一個類,看type的原始碼,通過type(name, bases, dict)
便可以生成一個新的類:
In [44]: test_class = type('Test', (), dict({'name': None}))
In [45]: a = test_class()
In [46]: a.name = 'Tony'
In [47]: a.name
Out[47]: 'Tony'
預設情況下,Python中類都是type類的例項:
In [12]: class A:
...: pass
...:
In [13]: A.__class__
Out[13]: type
In [14]: int.__class__
Out[14]: type
當你使用class關鍵字時,Python在幕後做的事情,就是通過元類來實現的。
- 為什麼重新定義
__call__
方法?
提出該問題是因為,與Python類建立相關的方法是:
__new__
:類方法,負責物件的建立,在定義類時需要返回一個例項,在我們通過類名進行例項化物件時自動呼叫。
__init__
:初始化函式,負責對new例項化的物件進行初始化,負責物件狀態的更新和屬性的設定,在每一次例項化物件之後呼叫。
而我們常用__call__
方法只是為了宣告這個類的物件是可呼叫的(callable)。
但是,在metaclass中__call__
方法還負責物件的建立,這就是為什麼要重新定義的原因了。
重定義了__call__
方法之後,一個物件的建立過程大概如下圖:
我們驗證一下:
class TestMetaClass(type):
def __init__(cls, what, bases=None, dict=None):
print("metaclass init")
super().__init__(what, bases, dict)
def __call__(cls, *args, **kwargs):
print("metaclass call")
self = super(TestMetaClass, cls).__call__(*args, **kwargs)
return self
class TestClass(metaclass=TestMetaClass):
def __init__(self, *args, **kwargs):
print("class init")
super().__init__()
def __new__(cls, *args, **kwargs):
print("class new")
self = super().__new__(cls)
return self
a = TestClass()
返回:
metaclass init
metaclass call
class new
class init
可以看到,__call__
方法在類執行__new__
和__init__
之前執行,這樣就可以解釋:
在Singleton中的__call__
方法對類屬性__instance
進行判斷:
- 如果
__instance
為None,表明類還未進行例項化,那麼給__instance
賦值為元類的父類(type)的__call__
方法。 - 如果
__instance
不為None,說明類已經進行過例項化,直接返回cls.__instance中的類例項。
便實現了單例模式。
除了重新定義__call__
以外,元類可以通過實現__init__
方法來定製例項,元類的__init__
方法可以做到類裝飾器能做到的任務事情,並且作用更大。
如果想要進一步定製類,可以在元類中實現__new__
方法。
另,編寫元類時,通常會把self引數改為cls,這樣能更清楚的表明要構建的例項是類。
元類的呼叫
上述例子中,都是通過metaclass=''
來設定類的元類,還可以這樣:
class TestClass():
__metaclass__ = TestMetaClass
def __init__(self, *args, **kwargs):
print("class init")
super().__init__()
在執行類定義時,直譯器會先尋找這個類屬性中的__metaclass__
,如果此屬性存在,就將這個屬性賦值給此類作為它的元類,如果此屬性沒有定義的話,就會向上查詢父類的__metaclass__
,如果沒有發現任何的父類,並且直譯器中也沒有名字為__metaclass__
的全域性變數,這個類就是傳統類,會使用type.ClassType作為此類的元類。
以上。