自訂元類

到現在,我們已經知道元類別是什麼東東了。那麼,從頭到尾我們還不知道元類到底有啥用。只是了解了一下元類。在了解它有啥用的時候,我們先來了解下怎麼自訂元類別。因為只有了解了怎麼自訂才能更好的理解它的作用。

首先我們來了解下__metaclass__ 屬性

metaclass,直譯為元類,簡單的解釋就是:

當我們定義了類別以後,就可以根據這個類別建立出實例,所以:先定義類,然後再建立實例。

但是如果我們想建立出類別呢?那就必須根據metaclass建立出類,所以:先定義metaclass,然後再建立類別。

連接起來就是:先定義metaclass,就可以建立類,最後再建立實例。

所以,metaclass允許你建立類別或修改類別。換句話說,你可以把類別看成是metaclass創建出來的「實例」。

class MyObject(object):
    __metaclass__ = something…
[…]

如果是這樣寫的話,Python 就會用元類別來建立類別 MyObject。當你寫下 class MyObject(object),但類別物件 MyObject 還沒有在記憶體中建立。 Python 會在類別的定義中尋找 __metaclass__ 屬性,如果找到了,Python 就會用它來建立類別 MyObject,如果沒有找到,就會用內建的 type 函數來建立這個類別。如果還不怎麼理解,看下下面的流程圖:

c7dd72cd3c802993425a3b6516a97e2.png

再舉個實例:

class Foo(Bar):
    pass

它的判斷流程是怎麼樣的呢?

首先判斷 Foo 中是否有 __metaclass__ 這個屬性?如果有,Python 會在記憶體中透過 __metaclass__ 建立一個名字為 Foo 的類別物件(注意,這裡是類別物件)。如果 Python 沒有找到__metaclass__ ,它會繼續在 Bar(父類)中尋找__metaclass__ 屬性,並嘗試做和前面相同的操作。如果 Python在任何父類別中都找不到 __metaclass__ ,它就會在模組層次中去尋找 __metaclass__ ,並嘗試做同樣的操作。如果還是找不到 __metaclass__ ,Python 就會用內建的 type 來建立這個類別物件。

其實 __metaclass__ 就是定義了 class 的行為。類似於 class 定義了 instance 的行為,metaclass 則定義了 class 的行為。可以說,class 是 metaclass 的 instance。

現在,我們基本上了解了 __metaclass__ 屬性,但是,也沒講過如何使用這個屬性,或者說這個屬性可以放些什麼?

答案就是:可以創建一個類別的東西。那什麼可以用來創建一個類別呢? type,或任何使用到 type 或子類化 type 的東東都可以。

元類別的主要目的就是為了當建立類別時能夠自動地改變類別。通常,你會為API 做這樣的事情,你希望可以建立符合目前上下文的類別。假想一個很愚蠢的例子,你決定在你的模組裡所有的類別的屬性都應該是大寫形式。有好幾種方法可以辦到,但其中一種就是透過在模組層級設定__metaclass__ 。採用這種方法,這個模組中的所有類別都會透過這個元類別來創建,我們只需要告訴元類別把所有的屬性都改成大寫形式就萬事大吉了。

幸運的是,__metaclass__ 實際上可以被任意調用,它並不需要是一個正式的類別。所以,我們在這裡就先以一個簡單的函數作為例子開始。

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
 
__metaclass__ = upper_attr  
#  这会作用到这个模块中的所有类
 
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'
用 class 当做元类的做法:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)

但是,這種方式其實不是 OOP。我們直接呼叫了 type,而且我們沒有改寫父類別的 __new__ 方法。現在讓我們這樣去處理:

class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

你可能已經注意到了有一個額外的參數 upperattr_metaclass ,這並沒有什麼特別的。類別方法的第一個參數總是表示目前的實例,就像在普通的類別方法中的 self 參數一樣。當然了,為了清晰起見,這裡的名字我起的比較長。但是就像 self 一樣,所有的參數都有它們的傳統名稱。因此,在真實的產品程式碼中一個元類別應該是像這樣的:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

如果使用super 方法的話,我們還可以使它變得更清晰一些,這會緩解繼承(是的,你可以擁有元類,從元類繼承,從type 繼承)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

通常我們都會使用元類去做一些晦澀的事情,依賴於自省,控制繼承等等。確實,用元類來搞些「黑暗魔法」是特別有用的,因而會搞出些複雜的東西來。但就元類別本身而言,它們其實是很簡單的:

攔截類別的創建

修改類別

返回修改之後的類別

繼續學習
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!