標籤:

Python · 元類(Meta Class)及其應用

(這裡是本章用到的 GitHub 地址)

  • 萬物皆對象 —— Python

本章所介紹的元類(Meta Class)和之前介紹過的裝飾器(Decorator)都是上面這句話的具現,其中裝飾器告訴過我們「函數亦對象」,元類則會告訴我們「類亦對象」

Meta Class 是傳說中的黑魔法、黑魔法中的戰鬥機(……)。我其實對它也只一知半解,所以以下說的內容可能僅展現了它神奇功用的冰山一角。不過作為一個入門教程來說的話、可能會剛剛好也說不定(其實只是在為自己的弱小找借口)(喂)

所謂的「類亦對象」和「函數亦對象」的思想類似:它意味著類可以被賦值給變數、通過變數也能創建該類的實例。舉個栗子:

class Class:n def __init__(self):n self.x = 1nnone = Classnprint(one().x)nOut[1]:n1n

正如裝飾器返回的是一個函數,我們可以認為元類返回的是一個類。 也正如我在講裝飾器里說過的,裝飾器的核心思想,就是裝飾函數這個對象、讓函數自身代碼不變的情況下、增添一些具有普適性的功能。在我看來,元類的核心思想,就是搗鼓類這個對象、使你能對其有著最高程度的控制權

注意:這絕不一定是個準確的理解!正如 Python界的領袖 Tim Peters 說過:

  • 元類就是深度的魔法,99%的用戶應該根本不必為此操心。如果你想搞清楚究竟是否需要用到元類,那麼你就不需要它。那些實際用到元類的人都非常清楚地知道他們需要做什麼,而且根本不需要解釋為什麼要用元類。

我的理解僅僅來自於我對元類的應用,它很有可能是非常片面的。不過,由於我的目的是為了讓大家知道元類的一種可能是最簡單的使用姿勢、使大家不至於看到代碼裡面的 metaclass 就怕,所以還請觀眾老爺們允許我這個半吊子繼續用這個理解講下去(如果有觀眾老爺有更深更好的理解、歡迎在評論區裡面教我、我會把它們貼在這裡的 ( σω)σ)

那麼什麼叫做最高程度的控制權呢?一個比較簡單的栗子就是實現如下需求:

  • 定義一個「人」(Person)類,它有三個方法:吃飯、睡覺、續幾秒(咦)

  • 定義 Person 的三個子類「小張」(Zhang)、「小王」(Wang)、「小江」(Jiang)
  • 定義「人」的子類「小紅」(Hong), 要求他:
    • 吃飯像小張一樣快
    • 睡覺像小王一樣香
    • 續秒像小江一樣熟練(喂)

你會怎麼去實現呢?如果再要求你把上面三個要求換一換順序呢?

也許 Python 有許多其它的解決方案、但(我所知道的)最簡單的方法、就是使用元類了

幸運的是,雖然元類的思想可能很深,但就這個簡單的問題而言、即使我不進行任何說明、相信聰明的觀眾老爺們也能讀懂下面這幾塊代碼

先定義 Person 類:

class Person:n def __init__(self):n self.ability = 1nn def eat(self):n print("Eat: ", self.ability)nn def sleep(self):n print("Sleep: ", self.ability)nn def save_life(self):n print("+ ", self.ability, " s")n

再定義三個子類:

class Wang(Person):n def eat(self):n print("Eat: ", self.ability * 2)nnclass Zhang(Person):n def sleep(self):n print("Sleep: ", self.ability * 2)nnclass Jiang(Person):n def save_life(self):n print("+ inf s")n

然後是最關鍵的、定義元類(Meta Class):

class Mixture(type):n def __new__(mcs, *args, **kwargs):n name, bases, attr = args[:3]n person1, person2, person3 = basesnn def eat(self):n person1.eat(self)nn def sleep(self):n person2.sleep(self)nn def save_life(self):n person3.save_life(self)nn attr["eat"] = eatn attr["sleep"] = sleepn attr["save_life"] = save_lifenn return type(name, bases, attr)n

Done!可能會有觀眾老爺發現其中有三行代碼顯得「特別傻」——沒錯,確實可以用更具有普適性的三行代碼來代替我們上面倒數第二到第四行的代碼:

class Mixture(type):n def __new__(mcs, *args, **kwargs):n name, bases, attr = args[:3]n person1, person2, person3 = basesnn def eat(self):n person1.eat(self)nn def sleep(self):n person2.sleep(self)nn def save_life(self):n person3.save_life(self)nn for key, value in locals().items():n if str(value).find("function") >= 0:n attr[key] = valuenn return type(name, bases, attr)n

拋開所有技術細節而只談應用的話、其實上面這個栗子可能已經相當足夠了。接下來就讓我們測試一下這個 Mixture元類吧。先來定義一個小的測試函數,它依次調用 Person 實例吃飯、睡覺、續幾秒這三個動作:

def test(person):n person.eat()n person.sleep()n person.save_life()n

然後進行兩組測試:

class Hong(Wang, Zhang, Jiang, metaclass=Mixture):n passnntest(Hong())nOut[2]:nEat: 2nSleep: 2n+ inf snnclass Hong(Zhang, Wang, Jiang, metaclass=Mixture):n passnntest(Hong())nOut[3]:nEat: 1nSleep: 1n+ inf sn

Done!可以看到、我們確實獲得了類的高度控制權

可能會有觀眾老爺想問,如果我直接繼承會發生什麼事情?就這個栗子而言,如果定義一個類直接繼承小王、小張、小江的話,無論按什麼順序繼承、結果都會是一樣的(猜猜這個結果會是什麼? ( σω)σ)

值得一提的是,可以看到我們定義的元類繼承自 type、這是因為 Python 自帶的元類就是 type

其實即使僅僅基於上述栗子的思想、就已經可以搗鼓出許多有意思的應用了。在我自己實現的神經網路中、我就用了這個思想來把普通 NN 裡面的附加層(Dropout、Normalize)擴展成了 CNN 裡面的附加層,感興趣的觀眾老爺們可以看看這裡面的 ConvSubMeta 類

我沒有講太多原理層面的東西,一方面是因為我覺得知道怎麼用就好、另一方面是因為我怕亂說話遭報應(……)

希望觀眾老爺們能夠喜歡~


推薦閱讀:

Python 與 機器學習 · 簡介
Python書單
Python之websocket web模擬tail -F。
mac os x如何安裝python科學計算庫numpy?
Python從零開始系列連載(1)——安裝環境

TAG:Python |