[譯]理解python的metaclass

前言

這篇博客是我在stackoverflow上看了一個提問回覆後寫的,例子基本用的都是e-satis本人的例子,語言組織也基本按照翻譯來。python

但我並非一個翻譯者,並不會嚴格遵照每行每句的翻譯;
有時候我會將表述換個順序,省略一些我認爲可有可無的話,以便讀者更好理解。web

因此,若是你不喜歡個人語言表述,或者想要看英文原文,能夠去查看原回覆數據庫

類也是對象

在理解metaclass以前,咱們先要掌握python中的類class是什麼。
python中類的概念,是借鑑自smalltalk語言。
在大部分語言中,類指的是"描述如何產生一個對象(object)"的一段代碼,這對於python也是如此。編程

>>> class ObjectCreator(object):
...     pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

可是,在python中,類遠不止如此,類同時也是對象。
當你遇到關鍵詞class的時候,python就會自動執行產生一個對象。下面的代碼段中:框架

>>> class ObjectCreator(object):
...     pass
...

python在內存中產生了一個名叫作"ObjectCreator"的對象。這個對象(類)自身擁有產生對象(實例instance)的能力。 這就是爲何稱呼這東西(後面遇到容易混淆的地方,咱們稱之爲:類對象)也是類的緣由。同時,它也是一個對象,所以你能夠對它作以下操做:函數

  • 賦值給變量
  • 複製它
  • 爲它增長屬性(attribute)
  • 做爲參數傳值給函數

舉例:工具

>>> print(ObjectCreator) # 你能夠打印一個類,由於它同時也是對象
<class '__main__.ObjectCreator'>

>>> def echo(o):
...     print(o)
...
>>> echo(ObjectCreator) # 做爲參數傳值給函數
<class '__main__.ObjectCreator'>

>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo

>>> ObjectCreatorMirror = ObjectCreator # 將類賦值給變量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

動態建立類

既然類也是對象,那麼咱們就能夠在運行的時候建立它,跟建立對象同樣天然。學習

首先,咱們使用class關鍵字定義一個產生類的函數:this

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

這很容易理解吧。可是,這並不那麼動態啊。咱們仍是須要本身來寫這個類的代碼。spa

既然類也是對象,那就應該有用來產生它的東西。這東西就是type

先來講說你所認識的type。這個古老而好用的函數,可讓咱們知道一個對象的類型是什麼。

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

實際上,type還有一個徹底不一樣的功能,它能夠在運行時產生類。type能夠傳入一些參數,而後返回一個類。(好吧,必須認可,根據不一樣的傳入參數,一個相同的函數type竟然會有兩個徹底不一樣的做用,這很愚蠢。不過python這樣作是爲了保持向後兼容性。)

下面舉例type建立類的用法。首先,對於類通常是這麼定義的:

>>> class MyShinyClass(object):
...     pass

在下面,MyShinyClass也能夠這樣子被建立出來,而且跟上面的建立方法有同樣的表現:

>>> 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>

type建立類須要傳入三個參數,分別爲:

  • 類的名字
  • 一組"類的父類"的元組(tuple) (這個會實現繼承,也能夠爲空)
  • 字典 (類的屬性名與值,key-value的形式,不傳至關於爲空,如通常寫法中的pass).

下面來點複雜的,來更好的理解type傳入的三個參數:

class Foo(object):
    bar = True

    def echo_bar(self):
        print(self.bar)

等價於:

def echo_bar(self):
    print(self.bar)

Foo = type('Foo', (), {'bar':True, 'echo_bar': echo_bar})

想要看點有繼承關係的類的實現,來:

class FooChild(Foo):
    pass

等價於:

FooChild = type('FooChild', (Foo, ), {})

回顧一下咱們學到哪了: 在python中,類就是對象,而且你能夠在運行的時候動態建立類.

那到底什麼是metaclass(元類)

metaclass 就是建立類的那傢伙。(事實上,type就是一個metaclass)

咱們知道,咱們定義了class就是爲了可以建立object的,沒錯吧?

咱們也學習了,python中類也是對象。

那麼,metaclass就是用來創造「類對象」的類.它是「類對象」的「類」。

能夠這樣子來理解:

圖片

MyClass = MetaClass()
    MyObject = MyClass()

也能夠用咱們上面學到的type來表示:

MyClass = type('MyClass', (), {})

說白了,函數type就是一個特殊的metaclass.
python在背後使用type創造了全部的類。type是全部類的metaclass.

咱們可使用__class__屬性來驗證這個說法.

在python中,一切皆爲對象:整數、字符串、函數、類.全部這些對象,都是經過類來創造的.

>>> 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'>

metaclass就是創造類對象的工具.若是你喜歡,你也能夠稱之爲"類的工廠".

type是python內置的metaclass。不過,你也能夠編寫本身的metaclass.

__metaclass__ 屬性

咱們能夠在一個類中加入 __metaclass__ 屬性.

class Foo(object):
        __metaclass__ = something...
        ......  # 省略

當你這麼作了,python就會使用metaclass來創造類:Foo。

注意啦,這裏有些技巧的。

當你寫下class Foo(object)的時候,類對象Foo尚未在內存中生成。

python會在類定義中尋找__metaclass__ 。若是找到了,python就會使用這個__metaclass__ 來創造類對象: Foo。若是沒找到,python就使用type來創造Foo。

請把下面的幾段話重複幾遍:

當你寫以下代碼的時候:

class Foo(Bar):
        pass

python作了如下事情:

Foo中有__metaclass__這個屬性嗎?
若是有,python會在內存中經過__metaclass__建立一個名字爲Foo的類對象。
若是python沒有在Foo中找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__,並嘗試作和前面一樣的操做。
若是python由下往上遍歷父類也都沒有找不到__metaclass__,它就會在模塊(module)中去尋找__metaclass__,並嘗試作一樣的操做。
若是仍是沒有找不到__metaclass__, python纔會用內置的type(這也是一個metaclass)來建立這個類對象。

如今問題來了,咱們要怎麼用代碼來實現__metaclass__呢? 寫一些能夠用來產生類(class)的東西就行。

那什麼能夠產生類?無疑就是type,或者type的任何子類,或者任何使用到type的東西都行.

自定義metaclass

使用metaclass的主要目的,是爲了可以在建立類的時候,自動地修改類。

一個很傻的需求,咱們決定要將該模塊中的全部類的屬性,改成大寫。

有幾種方法能夠作到,這裏使用__metaclass__來實現.

在模塊的層次定義metaclass,模塊中的全部類都會使用它來創造類。咱們只須要告訴metaclass,將全部的屬性轉化爲大寫。

# 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):

            uppercase_attr = {}
            for name, val in future_class_attr.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return type(future_class_name, future_class_parents, uppercase_attr)

這裏的方式其實不是OOP(面向對象編程).由於咱們直接調用了type,而不是改寫父類的__type__方法.

因此咱們也能夠這樣子處理:

class UpperAttrMetaclass(type):

        def __new__(upperattr_metaclass, future_class_name,
                    future_class_parents, future_class_attr):

            uppercase_attr = {}
            for name, val in future_class_attr.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return type.__new__(upperattr_metaclass, future_class_name,
                                future_class_parents, uppercase_attr)

這樣子看,咱們只是複用了 type.__new__方法,這就是咱們熟悉的基本的OOP編程,沒什麼魔法可言.

你可能注意到,__new__方法相比於

type(future_class_name, future_class_parents, future_class_attr)

多了一個參數: upperattr_metaclass, 請別在乎,這沒什麼特別的: __new__老是將"它要定義的類"做爲第一個參數。

這就比如是 self 在類的通常方法(method)中同樣,也是被做爲第一個參數傳入。

固然啦,這裏的名字的確是我起的太長了。就像self同樣,全部的參數都有它們傳統的名稱。
所以,在實際的代碼中,一個metaclass應該是寫成下面樣子的:

(咱們同時使用常見的super來讓代碼更清晰)

class UpperAttrMetaclass(type):

        def __new__(cls, clsname, bases, attrs):
            uppercase_attr = {}
            for name, val in attrs.items():
                if not name.startswith('__'):
                    uppercase_attr[name.upper()] = val
                else:
                    uppercase_attr[name] = val
            return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)

使用了 metaclass 的代碼是比較複雜,但咱們使用它的緣由並非爲了複雜, 而是由於咱們一般會使用 metaclass 去作一些晦澀的事情,好比, 依賴於自省,控制繼承等等。

確實,用 metaclass 來搞些「黑魔法」是特別有用的,於是會複雜化代碼。

但就metaclass自己而言,它們實際上是很簡單的:中斷類的默認建立、修改類、最後返回修改後的類.

到底爲何要使用metaclass

如今咱們面臨一個問題: 爲何要使用metaclass? 它容易出錯且晦澀難懂.

好吧,通常來講,咱們根本就用不上它, 99%的用戶應該根本沒必要爲此操心。

實際用到metaclass的人,很清楚他們到底須要作什麼,根本不用解釋爲何要用.

metaclass 的一個主要用途就是構建API。Django(一個python寫的web框架)的ORM 就是一個例子。

用Django先定義瞭如下Model:

class Person(models.Model):
        name = models.CharField(max_length=30)
        age = models.IntegerField()

而後執行下面代碼:

guy = Person.objects.get(name='bob')
    print guy.age  # result is 35

這裏打印的輸出並非IntegerField,而是一個intint是從數據庫中獲取的.

這是由於 models.Model 使用 __metaclass__來實現了複雜的數據庫查詢。但對於你看來,這就是簡單的API而已,不用關心背後的複雜工做。

結語

複習一下,咱們知道了,類是可以創造對象實例的對象,同時也是metaclass的對象實例(由於metaclass創造了它們).

在python中,一切皆爲對象。它們要麼是類的實例,要麼是metaclass的實例, 除了type。

type是它自身的metaclass。至因而怎麼實現的,總之純python語言是不可能實現的,這須要在實現層面上耍一些小手段才能作到的。

metaclass用起來比較複雜, 若是須要對很是簡單的類進行修改, 你可能不會使用它。有如下兩個技術能夠供你選擇:

相關文章
相關標籤/搜索