Python 「黑魔法」 之 Meta Classes

首發於 個人博客 轉載請註明出處html

接觸過 Django 的同窗都應該十分熟悉它的 ORM 系統。對於 python 新手而言,這是一項幾乎能夠被稱做「黑科技」的特性:只要你在models.py中隨便定義一個Model的子類,Django 即可以:python

  • 獲取它的字段定義,並轉換成表結構程序員

  • 讀取Meta內部類,並轉化成相應的配置信息。對於特殊的Model(如abstractproxy),還要進行相應的轉換安全

  • 爲沒有定義objectsModel加上一個默認的Manager框架

開發之餘,我也曾腦補過其背後的原理。曾經,我認爲是這樣的:ssh

啓動時,遍歷models.py中的全部屬性,找到Model的子類,並對其進行上述的修改。函數

當初,我還覺得本身觸碰到了真理,並曾將其應用到實際生產中——爲 SAE 的 KVDB 寫了一個類 ORM 系統。然而在實現的過程當中,我明顯感覺到了這種方法的醜陋,並且性能並不出色(由於要遍歷全部的定義模塊)。性能

那麼事實上,Django 是怎麼實現的呢?code

自古以來咱們製造東西的方法都是「自上而下」的,是用切削、分割、組合的方法來製造。然而,生命是自下而上地,自發地建造起來的,這個過程極爲低廉。
——王晉康 《水星播種》orm

這句話揭示了生命的神奇所在:真正的生命都是由基本物質自發構成的,而非造物主流水線式的加工

那麼,若是 類 也有生命的話,對它本身的修飾就不該該由調用者來完成,而應該是自發的

幸而,python 提供了造物主的接口——這即是 Meta Classes,或者稱爲「元類」。

元類 是什麼?

簡單說:元類就是類的類

首先,要有一個概念:

python 中,一切都是對象。

沒錯,一切,包括 類 自己。

既然,類 是 對象,對象 是 類的實例,那麼——類 也應該有 類 纔對。

類的類:type

在 python 中,咱們能夠用type檢測一個對象的類,如:

print type(1) # <type 'int'>

若是對一個類操做呢?

print type(int) # <type 'type'>

class MyClass(object): pass

print type(MyClass) # <type 'type'>

print type(type) # <type 'type'>

這說明:type實際上是一個類型,全部類——包括type本身——的類都是type

type 簡介

官方文檔 中,咱們能夠知道:

  • dict 相似,type 也是一個工廠構造函數,調用其將返回 一個type類型的實例(即 類)。

  • type 有兩個重載版本:

    • type(object),即咱們最經常使用的版本。

    • type(name, bases, dict),一個更強大的版本。經過指定 類名稱(name)、父類列表(bases)和 屬性字典(dict) 動態合成一個類。

下面兩個語句等價:

class Integer(int):

   name = 'my integer'

   def increase(self, num):
       return num + 1

    # -------------------

    Integer = type('Integer', (int, ), {
   'name': 'my integer',
   'increase': lambda self, num: \
                   num + 1    # 很酷的寫法,不是麼
    })

也就是說:類的定義過程,實際上是type類型實例化的過程

然而這和修飾一個已定義的類有什麼關係呢?

固然有啦~既然「類的定義」就是「type類型的初始化過程」,那其中一定會調用到type的構造函數(__new__()__init__())。只要咱們繼承 type類 並修改其 __new__函數,在這裏面動手腳就能夠啦。

接下來咱們將經過一個栗子感覺 python 的黑魔法,不過在此以前,咱們要先了解一個語法糖。

__metaclass__ 屬性

有沒以爲上面第二段示例有些鬼畜呢?它勒令程序員將類的成員寫成一個字典,簡直是反人類。若是咱們真的是要經過修改 元類 來改變 類 的行爲的話,彷佛就必須採用這種方法了~~簡直可怕~~

好在,python 2.2 時引進了一個語法糖:__metaclass__

class Integer(int):

    __metaclass__ = IntMeta

如今將會等價於:

Integer = IntMeta('Integer', (int, ), {})

由此一來,咱們在使用傳統類定義的同時,也可使用元類啦。

栗子:子類淨化器

需求描述

你是一個有語言潔癖的開發者,平時容不得別人講一句髒話,在開發時也是如此。如今,你寫出了一個很是棒的框架,並立刻要將它公之於衆了。不過,你的強迫症又犯了:若是你的使用者在代碼中寫滿了髒話,怎麼辦?豈不是玷污了本身的純潔?

假如你就是這個喪心病狂的開發者,你會怎麼作?

在知道元類以前,你可能會無從下手。不過,這個問題你能夠用 元類 輕鬆解決——只要在類定義時過濾掉不乾淨的字眼就行了(百度貼吧的幹活~~)。

咱們的元類看起來會是這樣的:

sensitive_words_list = ['asshole', 'fuck', 'shit']

def detect_sensitive_words(string):
    '''檢測敏感詞彙'''
    words_detected = filter(lambda word: word in string.lower(), sensitive_words_list)

    if words_detected:
        raise NameError('Sensitive words {0} detected in the string "{1}".' \
            .format(
                ', '.join(map(lambda s: '"%s"' % s, words_detected)),
                string
            )
        )

class CleanerMeta(type):

    def __new__(cls, class_name, bases, attrs):
        detect_sensitive_words(class_name) # 檢查類名
        map(detect_sensitive_words, attrs.iterkeys()) # 檢查屬性名

        print "Well done! You are a polite coder!" # 如無異常,輸出祝賀消息

        return super(CleanerMeta, cls).__new__(cls, class_name, bases, attrs)
        # 重要!這行必定不能漏!!這回調用內建的類構造器來構造類,不然定義好的類將會變成 None

如今,只需這樣定義基類:

class APIBase(object):

    __metaclass__ = CleanerMeta

    # ...

那麼全部 APIBase 的派生類都會接受安全審查(奸笑~~):

class ImAGoodBoy(APIBase):

    a_polite_attribute = 1

# [Output] Well done! You are a polite coder!

class FuckMyBoss(APIBase):

    pass

# [Output] NameError: Sensitive words "fuck" detected in the string "FuckMyBoss".

class PretendToBePolite(APIBase):

    def __fuck_your_asshole(self):
        pass

# [Output] NameError: Sensitive words "asshole", "fuck" detected in the string "_PretendToBePolite__fuck_your_asshole".

看,即便像最後一個例子中的私有屬性也難逃審查,由於它們本質都是相同的。

甚至,你還能夠對有問題的屬性進行偷偷的修改,好比 讓不文明的函數在調用時打出一行警告 等等,這裏就很少說了。

元類 在實際開發中的應用

平常開發時,元類 經常使用嗎?

固然,Django 的 ORM 就是一個例子,大名鼎鼎的 SQLAlchemy 也用了這種黑魔法。

此外,在一些小型的庫中,也有 元類 的身影。好比 abc(奇怪的名字~~)——這是 python 的一個內建庫,用於模擬 抽象基類(Abstract Base Classes)。開發者可使用 abc.abstractmethod 裝飾器,將 指定了 __metaclass__ = abc.ABCMeta 的類的方法定義成 抽象方法,同時這個類也成了 抽象基類,抽象基類是不可實例化的。這便實現了對 抽象基類 的模擬。

假若你也有須要動態修改類定義的需求,不妨也試試這種「黑魔法」。

小結

  • 類 也是 對象,全部的類都是type的實例

  • 元類(Meta Classes)是類的類

  • __metaclass__ = MetaMeta(name, bases, dict) 的 語法糖

  • 能夠經過重載元類的 __new__ 方法,修改 類定義 的行爲

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息