什么是元類:
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') # 輸出false print hasattr(Foo, 'BAR') # 輸出true f = 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