類型對象地位超然,負(fù)責(zé)創(chuàng)建對象實(shí)例,控制對象行為 (方法)。那么類型對象又由誰來創(chuàng)建呢?—— 元類 (metaclass),也就是類型的類型。
New-Style Class 的默認(rèn)元類是 type。
>>> class Data(object): pass
>>> Data.__class__
<type 'type'>
>>> type.__class__ # 最終的類型就是 type,包括 type 自己。
<type 'type'>
關(guān)鍵字 class 會被編譯成元類創(chuàng)建類型對象指令。
>>> Data = type("Data", (object,), {"x": 1}) # class 的實(shí)際行為。
>>> Data.__class__
<type 'type'>
>>> Data.__base__
<type 'object'>
>>> Data.x
1
正因?yàn)?class 和 def 一樣是指令,我們可以在任何地方創(chuàng)建類型對象。
>>> def test():
... class Data(object): pass
... return Data
>>> Data = test()
>>> Data.__name__
'Data'
>>> type(Data)
<type 'type'>
>>> Data()
<__main__.Data object at 0x10659f4d0>
現(xiàn)在可以理清幾者的關(guān)系,以及創(chuàng)建順序了。
class = metaclass(...) # 元類創(chuàng)建類型
instance = class(...) # 類型創(chuàng)建實(shí)例
instance.__class__ is class # 實(shí)例的類型
class.__class__ is metaclass # 類型的類型
metaclass
除了使用默認(rèn)元類 type 以外,還可以用 metaclass 屬性指定自定義元類,以便對類型對象創(chuàng)建過程進(jìn)行干預(yù)。
>>> class InjectMeta(type):
... def __new__(cls, name, bases, attrs):
... t = type.__new__(cls, name, bases, attrs)
...
... def print_id(self): print hex(id(self))
... t.print_id = print_id # 為類型對象添加實(shí)例方法。
... t.s = "Hello, World" # 添加靜態(tài)字段。
...
... return t
>>> class Data(object):
... __metaclass__ = InjectMeta # 顯式指定元類。
>>> Data.__metaclass__
<class '__main__.InjectMeta'>
>>> Data.__class__
<class '__main__.InjectMeta'>
>>> dir(Data)
['__class__', ... 'print_id', 's']
>>> Data.s
'Hello, World'
>>> Data().print_id()
0x10659d850
自定義元類通常都從 type 繼承,習(xí)慣以 Meta 結(jié)尾,就像抽象元類 abc.ABCMeta 那樣。代碼很簡單,只需注意 new 和 init 方法參數(shù)的區(qū)別就行了。
>>> class InjectMeta(type):
... def __new__(cls, name, bases, attrs):
... print "class:", cls # cls = InjectMeta
... print "name:", name
... print "bases:", bases
... print "attrs:", attrs
... return type.__new__(cls, name, bases, attrs)
...
... def __init__(cls, name, bases, attrs):
... print "class:", cls # cls = Data
... type.__init__(cls, name, bases, attrs)
>>> class Data(object):
... __metaclass__ = InjectMeta # 自定義元類
... x = 1
... def test(self): pass
class: <class '__main__.InjectMeta'>
name: Data
bases: (<type 'object'>,)
attrs: {
'test': <function test at 0x1065370c8>,
'x': 1,
'__module__': '__main__',
'__metaclass__': <class '__main__.InjectMeta'>
}
class: <class '__main__.Data'>
當(dāng)解釋器創(chuàng)建類型對象時,會按以下順序查找 metaclass 屬性。
class.__metaclass__ -> bases.__metaclass__ -> module.__metaclass__ -> type
這也是為什么在模塊中可以用 metaclass 為所有類型指定默認(rèn)元類的緣故。
雖然慣例將元類寫成 type 的派生類,但也可以用函數(shù)代替。
>>> def inject_meta(name, bases, attrs):
... t = type(name, bases, attrs)
... t.s = "Hello, World"
... return t
>>> class Data(object):
... __metaclass__ = inject_meta
>>> Data.__metaclass__
<unbound method Data.inject_meta>
>>> Data.s
'Hello, World'
magic
對象行為由類型決定,實(shí)例不過存儲了狀態(tài)數(shù)據(jù)。那么,當(dāng)我們控制了類型對象的創(chuàng)建,也就意味著可以讓對象的實(shí)際行為和代碼存在極大的差異。這是魔法的力量,也是 Python 核心開發(fā)人員 Tim Peters 說出下面這番話的原因 (想必你對他的 import this 很熟悉)。
Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why). Tim Peters (c.l.p post 2002-12-22)
試著寫兩個簡單的例子練練手。
靜態(tài)類 (static class): 不允許創(chuàng)建實(shí)例,通常作為工具類 (Utility) 存在。
>>> class StaticClassMeta(type):
... def __new__(cls, name, bases, attr):
... t = type.__new__(cls, name, bases, attr)
...
... def ctor(cls, *args, **kwargs):
... raise RuntimeError("Cannot create a instance of the static class")
... t.__new__ = staticmethod(ctor)
...
... return t
>>> class Data(object):
... __metaclass__ = StaticClassMeta
>>> Data()
RuntimeError: Cannot create a instance of the static class
密封類 (sealed class): 禁止被繼承。
>>> class SealedClassMeta(type):
... _types = set()
...
... def __init__(cls, name, bases, attrs):
... if cls._types & set(bases): # 判斷當(dāng)前類型基類是否是 sealed class。
... raise SyntaxError("Cannot inherit from a sealed class")
... cls._types.add(cls) # 將當(dāng)前類型加入到禁止繼承集合。
>>> class A(object):
... __metaclass__ = SealedClassMeta
>>> class B(A): pass
SyntaxError: Cannot inherit from a sealed class