Python 中的元類(metaclass)是一個(gè)深度魔法,平時(shí)我們可能比較少接觸到元類,本文將通過一些簡單的例子來理解這個(gè)魔法。
在 Python 中,一切皆對象。字符串,列表,字典,函數(shù)是對象,類也是一個(gè)對象,因此你可以:
看一個(gè)簡單的例子:
class Foo(object):
foo = True
class Bar(object):
bar = True
def echo(cls):
print cls
def select(name):
if name == 'foo':
return Foo # 返回值是一個(gè)類
if name == 'bar':
return Bar
>>> echo(Foo) # 把類作為參數(shù)傳遞給函數(shù) echo
<class '__main__.Foo'>
>>> cls = select('foo') # 函數(shù) select 的返回值是一個(gè)類,把它賦給變量 cls
>>> cls
__main__.Foo
在日常使用中,我們經(jīng)常使用 object 來派生一個(gè)類,事實(shí)上,在這種情況下,Python 解釋器會(huì)調(diào)用 type 來創(chuàng)建類。
這里,出現(xiàn)了 type,沒錯(cuò),是你知道的 type,我們經(jīng)常使用它來判斷一個(gè)對象的類型,比如:
class Foo(object):
Foo = True
>>> type(10)
<type 'int'>
>>> type('hello')
<type 'str'>
>>> type(Foo())
<class '__main__.Foo'>
>>> type(Foo)
<type 'type'>
事實(shí)上,type 除了可以返回對象的類型,它還可以被用來動(dòng)態(tài)地創(chuàng)建類(對象)。下面,我們看幾個(gè)例子,來消化一下這句話。
使用 type 來創(chuàng)建類(對象)的方式如下:
type(類名, 父類的元組(針對繼承的情況,可以為空),包含屬性和方法的字典(名稱和值))
假設(shè)有下面的類:
class Foo(object):
pass
現(xiàn)在,我們不使用 class 關(guān)鍵字來定義,而使用 type,如下:
Foo = type('Foo', (object, ), {}) # 使用 type 創(chuàng)建了一個(gè)類對象
上面兩種方式是等價(jià)的。我們看到,type 接收三個(gè)參數(shù):
在上面,我們使用 type() 創(chuàng)建了一個(gè)名為 Foo 的類,然后把它賦給了變量 Foo,我們當(dāng)然可以把它賦給其他變量,但是,此刻沒必要給自己找麻煩。
接著,我們看看使用:
>>> print Foo
<class '__main__.Foo'>
>>> print Foo()
<__main__.Foo object at 0x10c34f250>
假設(shè)有下面的類:
class Foo(object):
foo = True
def greet(self):
print 'hello world'
print self.foo
用 type 來創(chuàng)建這個(gè)類,如下:
def greet(self):
print 'hello world'
print self.foo
Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})
上面兩種方式的效果是一樣的,看下使用:
>>> f = Foo()
>>> f.foo
True
>>> f.greet
<bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
>>> f.greet()
hello world
True
再來看看繼承的情況,假設(shè)有如下的父類:
class Base(object):
pass
我們用 Base 派生一個(gè) Foo 類,如下:
class Foo(Base):
foo = True
改用 type 來創(chuàng)建,如下:
Foo = type('Foo', (Base, ), {'foo': True})
元類(metaclass)是用來創(chuàng)建類(對象)的可調(diào)用對象。這里的可調(diào)用對象可以是函數(shù)或者類等。但一般情況下,我們使用類作為元類。對于實(shí)例對象、類和元類,我們可以用下面的圖來描述:
類是實(shí)例對象的模板,元類是類的模板
+----------+ +----------+ +----------+
| | | | | |
| | instance of | | instance of | |
| instance +------------>+ class +------------>+ metaclass|
| | | | | |
| | | | | |
+----------+ +----------+ +----------+
我們在前面使用了 type 來創(chuàng)建類(對象),事實(shí)上,type 就是一個(gè)元類。
那么,元類到底有什么用呢?要你何用...
元類的主要目的是為了控制類的創(chuàng)建行為。我們還是先來看看一些例子,以消化這句話。
先從一個(gè)簡單的例子開始,假設(shè)有下面的類:
class Foo(object):
name = 'foo'
def bar(self):
print 'bar'
現(xiàn)在我們想給這個(gè)類的方法和屬性名稱前面加上 my_ 前綴,即 name 變成 my_name,bar 變成 my_bar,另外,我們還想加一個(gè) echo 方法。當(dāng)然,有很多種做法,這里展示用元類的做法。
1.首先,定義一個(gè)元類,按照默認(rèn)習(xí)慣,類名以 Metaclass 結(jié)尾,代碼如下:
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 給所有屬性和方法前面加上前綴 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 轉(zhuǎn)化為字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一個(gè) echo 方法
return type.__new__(cls, name, bases, _attrs) # 返回創(chuàng)建后的類
上面的代碼有幾個(gè)需要注意的點(diǎn):
type 繼承,這是因?yàn)?PrefixMetaclass 是用來創(chuàng)建類的__new__ 是在 __init__ 之前被調(diào)用的特殊方法,它用來創(chuàng)建對象并返回創(chuàng)建后的對象,對它的參數(shù)解釋如下:
2.接著,我們需要指示 Foo 使用 PrefixMetaclass 來定制類。
在 Python2 中,我們只需在 Foo 中加一個(gè) __metaclass__ 的屬性,如下:
class Foo(object):
__metaclass__ = PrefixMetaclass
name = 'foo'
def bar(self):
print 'bar'
在 Python3 中,這樣做:
class Foo(metaclass=PrefixMetaclass):
name = 'foo'
def bar(self):
print 'bar'
現(xiàn)在,讓我們看看使用:
>>> f = Foo()
>>> f.name # name 屬性已經(jīng)被改變
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-774-4511c8475833> in <module>()
----> 1 f.name
AttributeError: 'Foo' object has no attribute 'name'
>>>
>>> f.my_name
'foo'
>>> f.my_bar()
bar
>>> f.echo('hello')
'hello'
可以看到,F(xiàn)oo 原來的屬性 name 已經(jīng)變成了 my_name,而方法 bar 也變成了 my_bar,這就是元類的魔法。
再來看一個(gè)繼承的例子,下面是完整的代碼:
class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 給所有屬性和方法前面加上前綴 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 轉(zhuǎn)化為字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一個(gè) echo 方法
return type.__new__(cls, name, bases, _attrs)
class Foo(object):
__metaclass__ = PrefixMetaclass # 注意跟 Python3 的寫法有所區(qū)別
name = 'foo'
def bar(self):
print 'bar'
class Bar(Foo):
prop = 'bar'
其中,PrefixMetaclass 和 Foo 跟前面的定義是一樣的,只是新增了 Bar,它繼承自 Foo。先讓我們看看使用:
>>> b = Bar()
>>> b.prop # 發(fā)現(xiàn)沒這個(gè)屬性
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-778-825e0b6563ea> in <module>()
----> 1 b.prop
AttributeError: 'Bar' object has no attribute 'prop'
>>> b.my_prop
'bar'
>>> b.my_name
'foo'
>>> b.my_bar()
bar
>>> b.echo('hello')
'hello'
我們發(fā)現(xiàn),Bar 沒有 prop 這個(gè)屬性,但是有 my_prop 這個(gè)屬性,這是為什么呢?
原來,當(dāng)我們定義 class Bar(Foo) 時(shí),Python 會(huì)首先在當(dāng)前類,即 Bar 中尋找 __metaclass__,如果沒有找到,就會(huì)在父類 Foo 中尋找 __metaclass__,如果找不到,就繼續(xù)在 Foo 的父類尋找,如此繼續(xù)下去,如果在任何父類都找不到 __metaclass__,就會(huì)到模塊層次中尋找,如果還是找不到,就會(huì)用 type 來創(chuàng)建這個(gè)類。
這里,我們在 Foo 找到了 __metaclass__,Python 會(huì)使用 PrefixMetaclass 來創(chuàng)建 Bar,也就是說,元類會(huì)隱式地繼承到子類,雖然沒有顯示地在子類使用 __metaclass__,這也解釋了為什么 Bar 的 prop 屬性被動(dòng)態(tài)修改成了 my_prop。
寫到這里,不知道你理解元類了沒?希望理解了,如果沒理解,就多看幾遍吧~