摘要:幸而,提供了造物主的接口這便是,或者稱為元類。接下來我們將通過一個栗子感受的黑魔法,不過在此之前,我們要先了解一個語法糖。此外,在一些小型的庫中,也有元類的身影。
首發于 我的博客 轉載請注明出處
接觸過 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) #
如果對一個類操作呢?
print type(int) #class MyClass(object): pass print type(MyClass) # print 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__ = Meta 是 Meta(name, bases, dict) 的 語法糖
可以通過重載元類的 __new__ 方法,修改 類定義 的行為
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37753.html
摘要:我可以明確告訴你這不是,但它可以用解釋器運行。這種黑魔法,還要從說起。提案者設想使用一種特殊的文件首注釋,用于指定代碼的編碼。暴露了一個函數,用于注冊自定義編碼。所謂的黑魔法其實并不神秘,照貓畫虎定義好相應的接口即可。 首發于我的博客,轉載請注明出處 寫在前面 本文為科普文 本文中的例子在 Ubuntu 14.04 / Python 2.7.11 下運行成功,Python 3+ 的接...
摘要:先來看代碼吧,一會松哥再慢慢解釋關于這一段自動配置,解釋如下首先注解表明這是一個配置類。本文的案例,松哥已經上傳到上了,地址。我們使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中。Starter 為我們帶來了眾多的自動化配置,有了這些自動化配置,我們可以不費吹灰之力就能搭建一個生產級開發環境,有的小伙伴會覺得這個 Starter 好神奇呀!其實 Starter 也都...
摘要:主程序通過喚起子程序并傳入數據,子程序處理完后,用將自己掛起,并返回主程序,如此交替進行。通過輪詢或是等事件框架,捕獲返回的事件。從消息隊列中取出記錄,恢復協程函數。然而事實上只有直接操縱的協程函數才有可能接觸到這個對象。 首發于 我的博客 轉載請注明出處 寫在前面 本文默認讀者對 Python 生成器 有一定的了解,不了解者請移步至生成器 - 廖雪峰的官方網站。 本文基于 Pyth...
摘要:如果類型轉換你還不是很了解,可以先讀下這篇來理解一下從看隱式強制轉換機制。函數可對通過編碼的字符串進行解碼。而作者封裝的也是基于這兩者來實現輸出黑魔法字符串的。同時通過,返回了一個匿名函數形成了閉包。為了達到裝逼的效果。 寫在最前 事情的起因是這段看起來不像代碼的代碼: showImg(https://segmentfault.com/img/remote/14600000126810...
摘要:類的繼承類繼承有三種調用方式,其實是有區別的,聽我慢慢道來第一種父類方法參數直接調用第二種方法參數直接調用在誰的類下調用,就找此類對應的下一個就是要繼承的第三種方法參數找類名對應的的下一個,就是繼承的,一般寫本身的類名上下文管理器上下文管理 類的繼承 類繼承有三種調用方式,其實是 有區別 的,聽我慢慢道來 class A: def say(self, name): ...
閱讀 1206·2021-11-24 09:39
閱讀 2129·2021-11-22 13:54
閱讀 2111·2021-09-08 10:45
閱讀 1443·2021-08-09 13:43
閱讀 2985·2019-08-30 15:52
閱讀 3083·2019-08-29 15:38
閱讀 2848·2019-08-26 13:44
閱讀 3055·2019-08-26 13:30