python元類淺析

weixin_33728268發表於2017-08-29

宣告:本文僅限於簡書釋出,其他第三方網站均為盜版,原文地址: python元類淺析

在 python 的新式類中,元類可能是最難以理解的功能之一了,不僅元類這個概念難以理解,而且很多講解和闡述也是說得雲裡霧裡,我看過很多很多篇關於元類和 metaclass 的介紹,但是依舊不是很懂,知道我硬著頭皮看了兩篇官方的解析之後,才有點理解了,本文我將嘗試以一個比較簡單的角度來介紹元類,希望不會像我看過的文章一樣讓讀者也在雲裡霧裡。

什麼是元類

可能我們在學習 Python 的過程中,我們經常會聽過一個概念,那就是“在Python 中,一切都是物件”,那麼類呢?類也是物件麼?是的,類也是物件,那麼類既然是物件的話,它是誰的物件?

這個概念可能有點拗口,但是,事實上類是元類的物件,也就是類的類。元類的例項是類,類的例項是物件,就這麼簡單,我們可以這麼驗證:

5332460-3927ff7634790619.jpg

奇怪了對吧,對於 3 中的 a 的型別是 A,我們毫無異議,但是 A 的型別是 type 是什麼回事,那我們就反推一下,是不是可以這麼理解:

5332460-87cb6ddff95d389f.jpg

對,就是這麼理解,我們稱 a 是類 A 的物件,那麼,我們也要稱 A 是 type 的物件,那麼我們就可以將這裡的 type 理解為類的類,也就是大家所說的 元類 了。根據 Python 的官方文件[3],完整的構造形式是這樣的:

A = type('A', (object, ), dict({}))

class type(name, bases, dict)
With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.class.

The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.
With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the __dict__ attribute. For example, the following two statements create identical type objects:

元類何時用

雖然,剛才演示的是用 type 來構造類,但是,這並不是 Python 的常用用法,因為這種用法比較麻煩,而且侷限性比較大。既然是類的類,我們從上面看到,他可以建立類,那麼,能不能不建立類,而只修改類的屬性呢?事實上,Python 是可以的,根據 Python 官方文件[4] 所描述,預設情況下,類似通過 type() 來構建的;但是我們也可以通過在定義類的時候指定 metaclass 引數,來修改類的建立過程:

5332460-bcc91eb7871a638d.jpg

如何確定元類

可能你會奇怪,還需要知道如何確定元類?一個類的元類不是很明確嗎?不一定哦,就以我們上面一個例子來說,我們可以很明確知道 MyClass 的元類是 Meta,那麼 MySubclass 的元類呢?好像一下子不能肯定得答上來,下面再看看別人怎麼說,還是參考文件[4]

The appropriate metaclass for a class definition is determined as follows:

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

根據這些規則,我們就可以清晰得知道,我們的 MySubclass 的元類就是 Meta,基於的是第三條規則!

元類怎麼用

和 type 一樣,其實我們剛才也看到了,元類定義的時候通常傳遞三個引數,分別是:

  • 類名
  • 從基類繼承資料的元素
  • 屬性字典

這裡再舉個很簡單的例子:

5332460-7810261e31c652f3.jpg
15039763574338

都不用建立例項,只需要定義一個類,我們就可以看到一些輸出:

5332460-5bcb17ac0f848e91.jpg

這就是元類,可以發現元類是傳遞了三個引數,分別是:類名/基類列表和屬性字典。

元類實戰

那麼,元類到底有什麼用呢?用處其實有很多,我就不自己寫例項了,不然可能就會脫離實際,讓大家說沒有意義,要就來個實際的應用,就以 Flask 為例吧,看下 Flask 裡面是如何應用 元類 的,我看的是 Flask 0.12.2 版本,這個版本里面只用了一處的 元類,那就是 View 類裡面,程式碼為:

5332460-1ef71505e0b33b5b.jpg

這裡的用法比較奇特,它並沒有用到我們上面提到的兩種方法中的任何一種,反而是在 Line 9 中使用 type.new,構建了一個例項作為 MethodView 的父類,這種方法在參考資料 [5] 中作了詳細介紹。之所以用這種方式,是因為這種方式更加 OOP,因為我們可以在之前的例子裡面看到:

class MyClass(metaclass=Meta)

一點也不 OOP,因為無論是在 Java 中還是在 C++ 中,好像都沒有說繼承列表裡面帶引數名的吧?

Reference

  1. Core Python
  2. Understanding Python metaclasses
  3. Python Docs
  4. Customizing Class Creation
  5. What is a metaclass in Python?
  6. What is a metaclass in Python? 中文

相關文章