1.什么是類
在理解元類之前,我們必須先掌握Python中的類(class)。
和大多數(shù)語言一樣,Python中的類知識用來描述如何“生成一個對象”:
但是,在Python中,類不僅能用來描述如何生成一個對象, 類本身也是對象 。
在你使用關(guān)鍵詞** class **的時候,Python就會執(zhí)行它,并創(chuàng)建一個對象。
>> > class ObjectCreator(object):
... pass
...
上述指令在內(nèi)存中創(chuàng)建了一個“ObjectiveCreator”的對象。
這個對象(類)本身具有創(chuàng)建對象(實例)的能力,因此它也是一個類。你可以對它做以下操作:
1.將其分配給變量
2.復(fù)制它
3.為其添加屬性
4.將其作為函數(shù)參數(shù)傳遞
例如:
2.動態(tài)創(chuàng)建類
由于類是對象,因此你可以像創(chuàng)建任何對象(數(shù)組、字典等)一樣,隨時隨地創(chuàng)建類。
你甚至可以在函數(shù)里創(chuàng)建類:
但是,這樣的類并不是很動態(tài),因為你必須自己編寫整個類。
使用class關(guān)鍵字時,Python會幫你自動創(chuàng)建此對象,但是,Python同樣也提供了一種手動創(chuàng)建的方法,那就是type函數(shù)。
>> > print(type(1))
< type 'int' >
>> > print(type("1"))
< type 'str' >
>> > print(type(ObjectCreator))
< type 'type' >
>> > print(type(ObjectCreator()))
< class '__main__.ObjectCreator' >
type函數(shù)最經(jīng)典的用法是返回對象的類型。但是很少人知道,它還能接受參數(shù)并手動創(chuàng)建類。
type(name, bases, attrs)
其中
name
: 類名bases
: 元組,父類名attrs
: 字典,類屬性值
因此你可以這樣手動創(chuàng)建類:
>> > MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>> > print(MyShinyClass)
< class '__main__.MyShinyClass' >
>> > print(MyShinyClass()) # create an instance with the class
< __main__.MyShinyClass object at 0x8997cec >
如果你想給它賦予屬性,可以這樣玩:
>> > class Foo(object):
... bar = True
等同于
>> > Foo = type('Foo', (), {'bar':True})
用來繼承也是可以的:
>> > FooChild = type('FooChild', (Foo,), {})
>> > print(FooChild)
< class '__main__.FooChild' >
>> > print(FooChild.bar) # bar is inherited from Foo
True
可見通過 type() 函數(shù)創(chuàng)建的類和直接寫class是完全一樣的。
因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,然后調(diào)用 type() 函數(shù)創(chuàng)建出class。
正常情況下,我們用class來定義類,但是,type()函數(shù)也允許我們動態(tài)創(chuàng)建類,也就是說,動態(tài)語言本身支持運行期動態(tài)創(chuàng)建類,這和靜態(tài)語言有非常大的不同。
Python是通過什么做到這一切的?那就是元類。
3.什么是元類
元類就是用于創(chuàng)建類的“東西”。
你定義類是為了創(chuàng)建對象,Python中所有的類都是對象。元類是用于創(chuàng)建這些對象的??梢钥催@個例子:
MyClass = MetaClass()
my_object = MyClass()
這有點像套娃。這段代碼轉(zhuǎn)化為type就是這樣的:
MyClass = type('MyClass', (), {})
因此,我們可以得到一個基本事實,type 本身就是一個 元類 。
其實,就是 type 在幕后創(chuàng)建了Python中所有的類。
通過檢查__class__屬性,你會看到Python中,一切對象都是基于 type 的:
>> > age = 35
>> > age.__class__
< type 'int' >
>> > name = 'bob'
>> > name.__class__
< type 'str' >
>> > def foo(): pass
>> > foo.__class__
< type 'function' >
>> > class Bar(object): pass
>> > b = Bar()
>> > b.__class__
< class '__main__.Bar' >
那么,有個有趣的問題,__class__的__class__是什么呢?
>> > age.__class__.__class__
< type 'type' >
>> > name.__class__.__class__
< type 'type' >
>> > foo.__class__.__class__
< type 'type' >
>> > b.__class__.__class__
< type 'type' >
因此,元類只是創(chuàng)建類對象的東西,如果愿意,可以將其稱為“類的工廠”。
type 是Python使用的內(nèi)置元類。不過,你可以創(chuàng)建自己的元類。
3.1 __metaclass__屬性
在Python 2中,可以在編寫類時添加屬性__metaclass__,使用某個元類來創(chuàng)建該類:
class Foo(object):
__metaclass__ = something...
[...]
不過要小心的是,你雖然先寫了 class Foo(object),但Foo這個對象尚未被創(chuàng)建,Python將先尋找__metaclass__類,找到后用它來創(chuàng)建Foo類。
如果沒有這個__metaclass__類,它將使用 type 來創(chuàng)建類。
因此,類創(chuàng)建的流程是這樣的:
1.創(chuàng)建的類中有__metaclass__元類屬性嗎?
2.如果有,那就用__metaclass__給該類在內(nèi)存中創(chuàng)建一個類對象。
3.如果Python找不到
__metaclass__
,它將在MODULE級別查找__metaclass__屬性 。4.如果還是沒有,那就使用父類的元類來創(chuàng)建類對象。
現(xiàn)在的問題就是,你可以在__metaclass__中放置些什么代碼呢?
答案就是:可以創(chuàng)建一個類的東西。那么什么可以用來創(chuàng)建一個類呢?type,或者任何繼承或使用它的東西。
3.2 Python 3中的元類
設(shè)置元類的語法在Python3已改為:
class Foo(object, metaclass=something):
...
即不再使用__metaclass__屬性,而是在基類參數(shù)列表中引入關(guān)鍵字參數(shù)。
不過元類的基本工作方式不變。在Python3中,你可以將屬性作為關(guān)鍵字參數(shù)傳遞給元類:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
4.為什么需要元類
元類最主要的一個應(yīng)用方向是創(chuàng)建API,一個最著名的應(yīng)用是Django ORM,比如:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
當(dāng)你這樣訪問屬性的時候:
person = Person(name='bob', age='35')
print(person.age)
它并不會返回models.IntegerField,而是返回了一個整形的數(shù)字。
這是因為models.Model引用了一個ModelBase
類,該類隨后進行了魔術(shù)般地操作,使其能夠與數(shù)據(jù)庫字段進行掛鉤。
這就是元類的作用,Django通過它,完成了系列復(fù)雜的幕后工作,將原本非常復(fù)雜的事情變得非常簡單。
-
python
+關(guān)注
關(guān)注
55文章
4767瀏覽量
84375 -
數(shù)組
+關(guān)注
關(guān)注
1文章
412瀏覽量
25880 -
函數(shù)參數(shù)
+關(guān)注
關(guān)注
0文章
6瀏覽量
5979
發(fā)布評論請先 登錄
相關(guān)推薦
評論