Python 中 Meta Classes詳解
接觸過 Django 的同學(xué)都應(yīng)該十分熟悉它的 ORM 系統(tǒng)。對(duì)于 python 新手而言,這是一項(xiàng)幾乎可以被稱作“黑科技”的特性:只要你在models.py中隨便定義一個(gè)Model的子類,Django 便可以:
- 獲取它的字段定義,并轉(zhuǎn)換成表結(jié)構(gòu)
- 讀取Meta內(nèi)部類,并轉(zhuǎn)化成相應(yīng)的配置信息。對(duì)于特殊的Model(如abstract、proxy),還要進(jìn)行相應(yīng)的轉(zhuǎn)換
- 為沒有定義objects的Model加上一個(gè)默認(rèn)的Manager
開發(fā)之余,我也曾腦補(bǔ)過其背后的原理。曾經(jīng),我認(rèn)為是這樣的:
啟動(dòng)時(shí),遍歷models.py中的所有屬性,找到Model的子類,并對(duì)其進(jìn)行上述的修改。
當(dāng)初,我還以為自己觸碰到了真理,并曾將其應(yīng)用到實(shí)際生產(chǎn)中——為 SAE 的 KVDB 寫了一個(gè)類 ORM 系統(tǒng)。然而在實(shí)現(xiàn)的過程中,我明顯感受到了這種方法的丑陋,而且性能并不出色(因?yàn)橐闅v所有的定義模塊)。
那么事實(shí)上,Django 是怎么實(shí)現(xiàn)的呢?
自古以來我們制造東西的方法都是“自上而下”的,是用切削、分割、組合的方法來制造。然而,生命是自下而上地,自發(fā)地建造起來的,這個(gè)過程極為低廉。 ——王晉康 《水星播種》
這句話揭示了生命的神奇所在:真正的生命都是由基本物質(zhì)自發(fā)構(gòu)成的,而非造物主流水線式的加工。
那么,如果 類 也有生命的話,對(duì)它自己的修飾就不應(yīng)該由調(diào)用者來完成,而應(yīng)該是自發(fā)的。
幸而,python 提供了造物主的接口——這便是 Meta Classes,或者稱為“元類”。
元類 是什么?
簡單說:元類就是類的類。
首先,要有一個(gè)概念:
python 中,一切都是對(duì)象。
沒錯(cuò),一切,包括 類 本身。
既然,類 是 對(duì)象,對(duì)象 是 類的實(shí)例,那么——類 也應(yīng)該有 類 才對(duì)。
類的類:type
在 python 中,我們可以用type檢測(cè)一個(gè)對(duì)象的類,如:
print type(1) # <type 'int'>
如果對(duì)一個(gè)類操作呢?
print type(int) # <type 'type'> class MyClass(object): pass print type(MyClass) # <type 'type'> print type(type) # <type 'type'>
這說明:type其實(shí)是一個(gè)類型,所有類——包括type自己——的類都是type。
type 簡介
從 官方文檔 中,我們可以知道:
和 dict 類似,type 也是一個(gè)工廠構(gòu)造函數(shù),調(diào)用其將返回 一個(gè)type類型的實(shí)例(即 類)。
type 有兩個(gè)重載版本:
+ `type(object)`,即我們最常用的版本。
+ `type(name, bases, dict)`,一個(gè)更強(qiáng)大的版本。通過指定 類名稱(`name`)、父類列表(`bases`)和 屬性字典(`dict`) 動(dòng)態(tài)合成一個(gè)類。
下面兩個(gè)語句等價(jià):
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 # 很酷的寫法,不是么 })
也就是說:類的定義過程,其實(shí)是type類型實(shí)例化的過程。
然而這和修飾一個(gè)已定義的類有什么關(guān)系呢?
當(dāng)然有啦~既然“類的定義”就是“type類型的初始化過程”,那其中必定會(huì)調(diào)用到type的構(gòu)造函數(shù)(__new__() 或 __init__())。只要我們繼承 type類 并修改其 __new__函數(shù),在這里面動(dòng)手腳就可以啦。
接下來我們將通過一個(gè)栗子感受 python 的黑魔法,不過在此之前,我們要先了解一個(gè)語法糖。
__metaclass__ 屬性
有沒覺得上面第二段示例有些鬼畜呢?它勒令程序員將類的成員寫成一個(gè)字典,簡直是反人類。如果我們真的是要通過修改 元類 來改變 類 的行為的話,似乎就必須采用這種方法了~~簡直可怕~~
好在,python 2.2 時(shí)引進(jìn)了一個(gè)語法糖:__metaclass__。
class Integer(int): __metaclass__ = IntMeta
現(xiàn)在將會(huì)等價(jià)于:
Integer = IntMeta('Integer', (int, ), {})
由此一來,我們?cè)谑褂脗鹘y(tǒng)類定義的同時(shí),也可以使用元類啦。
栗子:子類凈化器
需求描述
你是一個(gè)有語言潔癖的開發(fā)者,平時(shí)容不得別人講一句臟話,在開發(fā)時(shí)也是如此。現(xiàn)在,你寫出了一個(gè)非常棒的框架,并馬上要將它公之于眾了。不過,你的強(qiáng)迫癥又犯了:如果你的使用者在代碼中寫滿了臟話,怎么辦?豈不是玷污了自己的純潔?
假如你就是這個(gè)喪心病狂的開發(fā)者,你會(huì)怎么做?
在知道元類之前,你可能會(huì)無從下手。不過,這個(gè)問題你可以用 元類 輕松解決——只要在類定義時(shí)過濾掉不干凈的字眼就好了(百度貼吧的干活~~)。
我們的元類看起來會(huì)是這樣的:
sensitive_words_list = ['asshole', 'fuck', 'shit'] def detect_sensitive_words(string): '''檢測(cè)敏感詞匯''' 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) # 重要!這行一定不能漏?。∵@回調(diào)用內(nèi)建的類構(gòu)造器來構(gòu)造類,否則定義好的類將會(huì)變成 None 現(xiàn)在,只需這樣定義基類: class APIBase(object): __metaclass__ = CleanerMeta # ... 那么所有 APIBase 的派生類都會(huì)接受安全審查(奸笑~~): 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".
看,即使像最后一個(gè)例子中的私有屬性也難逃審查,因?yàn)樗鼈儽举|(zhì)都是相同的。
甚至,你還可以對(duì)有問題的屬性進(jìn)行偷偷的修改,比如 讓不文明的函數(shù)在調(diào)用時(shí)打出一行警告 等等,這里就不多說了。
元類 在實(shí)際開發(fā)中的應(yīng)用
日常開發(fā)時(shí),元類 常用嗎?
當(dāng)然,Django 的 ORM 就是一個(gè)例子,大名鼎鼎的 SQLAlchemy 也用了這種黑魔法。
此外,在一些小型的庫中,也有 元類 的身影。比如 abc(奇怪的名字~~)——這是 python 的一個(gè)內(nèi)建庫,用于模擬 抽象基類(Abstract Base Classes)。開發(fā)者可以使用 abc.abstractmethod 裝飾器,將 指定了 __metaclass__ = abc.ABCMeta 的類的方法定義成 抽象方法,同時(shí)這個(gè)類也成了 抽象基類,抽象基類是不可實(shí)例化的。這便實(shí)現(xiàn)了對(duì) 抽象基類 的模擬。
倘若你也有需要?jiǎng)討B(tài)修改類定義的需求,不妨也試試這種“黑魔法”。
小結(jié)
- 類 也是 對(duì)象,所有的類都是type的實(shí)例
- 元類(Meta Classes)是類的類
- __metaclass__ = Meta 是 Meta(name, bases, dict) 的 語法糖
- 可以通過重載元類的 __new__ 方法,修改 類定義 的行為
相關(guān)文章
Python實(shí)現(xiàn)arctan換算角度的示例
本文主要介紹了Python實(shí)現(xiàn)arctan換算角度的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03django 解決自定義序列化返回處理數(shù)據(jù)為null的問題
這篇文章主要介紹了django 解決自定義序列化返回處理數(shù)據(jù)為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-05-05Python使用logging實(shí)現(xiàn)多進(jìn)程安全的日志模塊
這篇文章主要為大家詳細(xì)介紹了Python如何使用標(biāo)準(zhǔn)庫logging實(shí)現(xiàn)多進(jìn)程安全的日志模塊,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-01-01python隨機(jī)數(shù)分布random均勻分布實(shí)例
今天小編就為大家分享一篇python隨機(jī)數(shù)分布random均勻分布實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11Jmeter如何使用BeanShell取樣器調(diào)用Python腳本
這篇文章主要介紹了Jmeter使用BeanShell取樣器調(diào)用Python腳本,文章圍繞Jmeter調(diào)用Python腳本的相關(guān)詳情展開標(biāo)題內(nèi)容,需要的小伙伴可以參考一下2022-03-03Python與數(shù)據(jù)庫的交互問題小結(jié)
這篇文章主要介紹了Python與數(shù)據(jù)庫的交互,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12