python中的__metaclass__


什么是元類:

  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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM