Python “黑魔法” 之 Meta Classes

tanhong_9999 8年前發布 | 9K 次閱讀 Python Python開發

來自: https://segmentfault.com/a/1190000004426130

首發于 我的博客 轉載請注明出處

</div>

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

  • 獲取它的字段定義,并轉換成表結構

  • 讀取 Meta 內部類,并轉化成相應的配置信息。對于特殊的 Model (如 abstract 、 proxy ),還要進行相應的轉換

  • 為沒有定義 objects 的 Model 加上一個默認的 Manager

開發之余,我也曾腦補過其背后的原理。曾經,我認為是這樣的:

啟動時,遍歷 models.py 中的所有屬性,找到 Model 的子類,并對其進行上述的修改。

當初,我還以為自己觸碰到了真理,并曾將其應用到實際生產中——為 SAE 的 KVDB 寫了一個類 ORM 系統。然而在實現的過程中,我明顯感受到了這種方法的丑陋,而且性能并不出色(因為要遍歷所有的定義模塊)。

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

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

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

那么,如果 類 也有生命的話,對它自己的修飾就不應該由調用者來完成,而應該是 自發的

幸而,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'></pre>

這說明: 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 # 很酷的寫法,不是么 })</pre>

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

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

當然有啦~既然“類的定義”就是“ type 類型的初始化過程”,那其中必定會調用到 type 的構造函數( __new__() 或 __init__() )。只要我們繼承 type 類 并修改其 __new__ 函數,在這里面動手腳就可以啦。

接下來我們將通過一個栗子感受 python 的黑魔法,不過在此之前,我們要先了解一個語法糖。

__metaclass__ 屬性

有沒覺得上面第二段示例有些鬼畜呢?它勒令程序員將類的成員寫成一個字典,簡直是反人類。如果我們真的是要通過修改 元類 來改變 類 的行為的話,似乎就必須采用這種方法了~~簡直可怕~~

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

class Integer(int):

__metaclass__ = IntMeta</pre> 

現在將會等價于:

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

現在,只需這樣定義基類:

class APIBase(object):

__metaclass__ = CleanerMeta

# ...</pre> 

那么所有 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".</pre>

看,即使像最后一個例子中的私有屬性也難逃審查,因為它們本質都是相同的。

甚至,你還可以對有問題的屬性進行偷偷的修改,比如 讓不文明的函數在調用時打出一行警告 等等,這里就不多說了。

元類 在實際開發中的應用

日常開發時,元類 常用嗎?

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

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

倘若你也有需要動態修改類定義的需求,不妨也試試這種“黑魔法”。

小結

  • 類 也是 對象,所有的類都是 type 的實例

  • 元類(Meta Classes)是類的類

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

  • 可以通過重載元類的 __new__ 方法,修改 類定義 的行為

  • </ul> </div>

 本文由用戶 tanhong_9999 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!