標籤:
什麼是元類:
python中類也是一種對象, 可以稱為類對象.
元類就是用來建立類對象的"東西". 你建立類就是為了建立類的執行個體對象, 不是嗎? 但是我們已經學習了python中的類也是對象. 元類就是用來建立這些類對象的, 元類就是類的類, 你可以這樣理解:
MyClass = MetaClass()MyObject = MyClass()
你已經看到了type可以這樣來動態建立類:
MyClass = type("MyClass", (), {})
這是因為type實際上是一個元類. type就是python在背後用來建立所有類的元類. 那麼現在你肯定特別想知道type既然是一個類(元類), 為什麼首字母沒有大寫? 好吧, 我猜這是為了與int, str, function這些python內建資料類型保持一致, str是用來建立字串對象的類, 而int是用來建立整數對象的類, type就是用來建立類對象的類. 你可以通過檢查__class__屬性來證明這一點. python中所有的東西都是對象(這一點和javascript相同), 這包括整數, 字串, 函數以及類, 它們全部都是對象, 而且它們都是從一個類建立而來, 這個類就是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‘>>>> Bar.__class__<type ‘type‘>
從上面結果可以看出, python中的所有普通的類也是對象, 當然這些類對象也有類型. 可以看出, int, str, 函數, 普通自訂類, 這些類對象的類型都是type.
元類就是建立類對象的類. 如果你喜歡, 可以把元類看做"類工廠". type就是python的內建元類, 當然也可以建立自己的元類.
__metaclass__屬性:
你可以在實現一個類的時候為其添加__metaclass__屬性:
class Foo(object): __metaclass__ = something ...
如果你這麼做了, python就會用元類來建立類Foo. 當在記憶體中建立類對象Foo時, python會在類的定義中尋找__metaclass__屬性, 如果找到了, python就用它來建立Foo, 如果沒有找到, 就會用內建的type元類來建立這個類.
當你實現一個有繼承的類時:
class Foo(Bar): pass
python會做如下操作:
Foo中有__metaclass__這個屬性嗎? 如果是, python會在記憶體中通過__metaclass__建立一個名字為Foo的類對象. 如果python沒有找到__metaclass__, 它會繼續在Bar(父類)中尋找__metaclass__, 以此類推. 如果python在任何父類中都沒有找到__metaclass__, 它就會在模組層次中尋找__metaclass__. 如果還是找不到__metaclass__, python就會用內建的type元類來建立這個類對象.
自訂元類:
元類的主要目的是為了當建立類時能夠自動的改變類, 或者說定製類. 為什麼這麼說呢, 我們可以這樣理解: 把類的建立過成想象成一個管道, 如果遍曆完繼承鏈+模組層次都沒有發現__metaclass__屬性, 那麼用type元類來建立該類. 這種情況下我們在類中怎樣定義屬性, 最終產生的類對象也會具有怎樣的屬性, 如所示:
類建立時的樣子---(type)-->類最終的樣子(由於是用type元類, 所以類最終樣式與類建立時樣式一樣)
如果遍曆過程中找到了__metaclass__屬性, 那麼就會用自訂元類來建立該類, 如所示:
類建立時的樣子---(__metaclass__屬性指向的元類)--->類最終的樣子
由於在我們自訂的元類裡面可以定製那些__metaclass__指過來的類, 因此元類的主要目的一般是為了當建立類時自動的改變類, 或者說定製類.
到底什麼時候才會用到元類呢? 通常你會為API做這樣的事情, 你希望可以建立複合當前內容相關的類. 假想一個很傻的例子, 你希望在你的模組裡所有類的屬性都應該是大寫形式. 有好幾種方法可以辦到, 但其中一種就是通過在模組層級別設定__metaclass__. 採用這種方法, 這個模組中的所有類都會通過這個元類來建立, 我們只需要告訴元類把所有的屬性都改成大寫形式就行:
# !/usr/bin/python# -*- coding: utf-8 -*-# type實際上是一個類, 就像str和int一樣, 所以你可以從type繼承class UpperAttrMetaClass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith(‘__‘)) uppercase_attrs = dict((name.upper(), value) for name, value in attrs) return type.__new__(cls, name, bases, uppercase_attrs)class Foo(object): __metaclass__ = UpperAttrMetaClass bar = ‘bip‘print hasattr(Foo, ‘bar‘)# 輸出falseprint hasattr(Foo, ‘BAR‘)# 輸出truef = Foo()print f.BAR# 輸出bip
也可以使用super方法:
class UpperAttrMetaClass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith(‘__‘)) uppercase_attrs = dict((name.upper(), value) for name, value in attrs) #return type.__new__(cls, name, bases, uppercase_attrs) return super(UpperAttrMetaClass, cls).__new__(cls, name, bases, uppercase_attrs)
就是這樣, 除此之外關於元類真的沒有別的可以說的了. 就元類本身而言, 它其實很簡單:
1. 攔截類的建立
2. 修改類(或者說定製類)
3. 返回修改之後的類
究竟為什麼要使用元類:
元類的主要作用是建立API. 一個典型的例子是Django ORM. 它允許你像這樣定義類:
class Person(models.Model): name = models.CharField(max_length = 30) age = models.IntegerField()
但是如果你像這樣做的話:
guy = Person(name = "bob", age = "35")print guy.age
調用guy.age並不會返回一個IntegerField對象, 而是會返回一個int, 是指可以直接從資料庫中取出資料. 這是有可能的, 因為models.Model定義了__metaclass__, 並且在元類中將你剛剛定義的簡單的Person類給修改了, 變的能夠訪問資料庫了. Django架構將這些看起來很複雜的東西通過暴露出一個簡單的使用元類的API(models.Model)將其化簡, 通過這個API重新建立代碼, 在背後完成真正的工作.
結語:
現在我們知道了, 類本身也是執行個體, 只不過它們是元類的執行個體. python中的一切都是對象, 這些對象要麼是類對象的執行個體, 要麼是元類的執行個體, 除了type. 元類是比較複雜的, 大多數情況下, 我們不會對類做修改. 如果需要對類做修改, 大多數情況下我們會通過下面兩種技術來動態修改類:
1. Monkey patching
2. class decorators
python中的__metaclass__