python元類解析
分享下自己對python的metaclass的知識。
一 你可以從這裡獲取什麼?
1. 也許你在閱讀別人的程式碼的時候碰到過metaclass,那你可以參考這裡的介紹。
2. 或許你需要設計一些底層的庫,也許metaclass能幫你簡化你的設計(也有可能複雜化:)
3. 也許你在瞭解metaclass的相關知識之後,你對python的類的一些機制會更瞭解。
4. more......
二 metaclass的作用是什麼?(感性認識)
metaclass能有什麼用處,先來個感性的認識:
1. 你可以自由的、動態的修改/增加/刪除 類的或者例項中的方法或者屬性
2. 批量的對某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
3. 當引入第三方庫的時候,如果該庫某些類需要patch的時候可以用metaclass
4. 可以用於序列化(參見yaml這個庫的實現,我沒怎麼仔細看)
5. 提供介面註冊,介面格式檢查等
6. 自動委託(auto delegate)
7. more...
三 metaclass的相關知識
1. what is metaclass?
1.1 在wiki上面,metaclass是這樣定義的:In object-oriented programming,
a metaclass is a class whose instances are classes.
Just as an ordinary class defines the behavior of certain objects,
a metaclass defines the behavior of certain classes and their instances.
也就是說metaclass的例項化結果是類,而class例項化的結果是instance。我是這麼理解的:
metaclass是類似建立類的模板,所有的類都是通過他來create的(呼叫__new__),這使得你可以自由的控制
建立類的那個過程,實現你所需要的功能。
1.2 metaclass基礎
* 一般情況下, 如果你要用類來實現metaclass的話,該類需要繼承於type,而且通常會重寫type的__new__方法來控制建立過程。
當然你也可以用函式的方式(下文會講)
* 在metaclass裡面定義的方法會成為類的方法,可以直接通過類名來呼叫
2. 如何使用metaclass
2.1 用類的形式
2.1.1 類繼承於type, 例如: class Meta(type):pass
2.1.2 將需要使用metaclass來構建class的類的__metaclass__屬性(不需要顯示宣告,直接有的了)賦值為Meta(繼承於type的類)
2.2 用函式的形式
2.2.1 構建一個函式,例如叫metaclass_new, 需要3個引數:name, bases, attrs,
name: 類的名字
bases: 基類,通常是tuple型別
attrs: dict型別,就是類的屬性或者函式
2.2.2 將需要使用metaclass來構建class的類的__metaclass__屬性(不需要顯示宣告,直接有的了)賦值為函式metaclas_new
3 metaclass 原理
3.1 basic
metaclass的原理其實是這樣的:當定義好類之後,建立類的時候其實是呼叫了type的__new__方法為這個類分配記憶體空間,建立
好了之後再呼叫type的__init__方法初始化(做一些賦值等)。所以metaclass的所有magic其實就在於這個__new__方法裡面了。
說說這個方法:__new__(cls, name, bases, attrs)
cls: 將要建立的類,類似與self,但是self指向的是instance,而這裡cls指向的是class
name: 類的名字,也就是我們通常用類名.__name__獲取的。
bases: 基類
attrs: 屬性的dict。dict的內容可以是變數(類屬性),也可以是函式(類方法)。
所以在建立類的過程,我們可以在這個函式裡面修改name,bases,attrs的值來自由的達到我們的功能。這裡常用的配合方法是
getattr和setattr(just an advice)
3.2 查詢順序
再說說關於__metaclass__這個屬性。這個屬性的說明是這樣的:
This variable can be any callable accepting arguments for name, bases, and dict. Upon class creation, the callable is used instead of the built-in type(). New in version 2.2.(所以有了上面介紹的分別用類或者函式的方法)
The appropriate metaclass is determined by the following precedence rules:
If dict['__metaclass__'] exists, it is used.
Otherwise, if there is at least one base class, its metaclass is used (this looks for a __class__ attribute first and if not found, uses its type).
Otherwise, if a global variable named __metaclass__ exists, it is used.
Otherwise, the old-style, classic metaclass (types.ClassType) is used.
這個查詢順序也比較好懂,而且利用這個順序的話,如果是old-style類的話,可以在某個需要的模組裡面指定全域性變數
__metaclass__ = type就能把所有的old-style 變成 new-style的類了。(這是其中一種trick)
四 例子
針對第二點說的metaclass的作用,順序來給些例子:
1. 你可以自由的、動態的修改/增加/刪除 類的或者例項中的方法或者屬性
Python程式碼- #!/usr/bin/python
- #coding :utf-8
- def ma(cls):
- print 'method a'
- def mb(cls):
- print 'method b'
- method_dict = {
- 'ma': ma,
- 'mb': mb,
- }
- class DynamicMethod(type):
- def __new__(cls, name, bases, dct):
- if name[:3] == 'Abc':
- dct.update(method_dict)
- return type.__new__(cls, name, bases, dct)
- def __init__(cls, name, bases, dct):
- super(DynamicMethod, cls).__init__(name, bases, dct)
- class AbcTest(object):
- __metaclass__ = DynamicMethod
- def mc(self, x):
- print x * 3
- class NotAbc(object):
- __metaclass__ = DynamicMethod
- def md(self, x):
- print x * 3
- def main():
- a = AbcTest()
- a.mc(3)
- a.ma()
- print dir(a)
- b = NotAbc()
- print dir(b)
- if __name__ == '__main__':
- main()
通過DynamicMethod這個metaclass的原型,我們可以在那些指定了__metaclass__屬性位DynamicMethod的類裡面,
根據類名字,如果是以'Abc'開頭的就給它加上ma和mb的方法(這裡的條件只是一種簡單的例子假設了,實際應用上
可能更復雜),如果不是'Abc'開頭的類就不加. 這樣就可以打到動態新增方法的效果了。其實,你也可以將需要動態
新增或者修改的方法改到__new__裡面,因為python是支援在方法裡面再定義方法的. 通過這個例子,其實可以看到
只要我們能操作__new__方法裡面的其中一個引數attrs,就可以動態新增東西了。
2. 批量的對某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
這個其實有應用場景的,就是比如我們cgi程式裡面,很多需要驗證登入或者是否有許可權的,只有驗證通過之後才
可以操作。那麼如果你有很多個操作都需要這樣做,我們一般情況下可以手動在每個方法的前頭用@login_required
類似這樣的方式。那如果學習了metaclass的使用的話,這次你也可以這樣做:
Python程式碼- #!/usr/bin/python
- #coding :utf-8
- from types import FunctionType
- def login_required(func):
- print 'login check logic here'
- return func
- class LoginDecorator(type):
- def __new__(cls, name, bases, dct):
- for name, value in dct.iteritems():
- if name not in ('__metaclass__', '__init__', '__module__') and\
- type(value) == FunctionType:
- value = login_required(value)
- dct[name] = value
- return type.__new__(cls, name, bases, dct)
- class Operation(object):
- __metaclass__ = LoginDecorator
- def delete(self, x):
- print 'deleted %s' % str(x)
- def main():
- op = Operation()
- op.delete('test')
- if __name__ == '__main__':
- main()
這樣子你就可以不用在delete函式上面寫@login_required, 也能達到decorator的效果了。不過可讀性就差點了。
3. 當引入第三方庫的時候,如果該庫某些類需要patch的時候可以用metaclass
Python程式碼- #!/usr/bin/python
- #coding :utf-8
- def monkey_patch(name, bases, dct):
- assert len(bases) == 1
- base = bases[0]
- for name, value in dct.iteritems():
- if name not in ('__module__', '__metaclass__'):
- setattr(base, name, value)
- return base
- class A(object):
- def a(self):
- print 'i am A object'
- class PatchA(A):
- __metaclass__ = monkey_patch
- def patcha_method(self):
- print 'this is a method patched for class A'
- def main():
- pa = PatchA()
- pa.patcha_method()
- pa.a()
- print dir(pa)
- print dir(PatchA)
- if __name__ == '__main__':
- main()
5. 提供介面註冊,介面格式檢查等, 這個功能可以參考這篇文章:
6. 自動委託(auto delegate)
以下是網上的例子:
http://marlonyao.iteye.com/blog/762156
五 總結
1. metaclass的使用原則:
If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why). --Tim Peters
也就是說如果你不知道能用metaclass來幹什麼的話,你儘量不要用,因為通常metaclass的程式碼會增加程式碼的複雜度,
降低程式碼的可讀性。所以你必需權衡metaclass的利弊。
2. metaclass的優勢在於它的動態性和可描述性(比如上面例子中的self.delegate.__getitem__(i)這樣的程式碼,它
可以用另外的函式程式碼生成,而無需每次手動編寫), 它能把類的動態性擴充套件到極致。
六 補充
以下是同事們的很好的補充:
張同學:
1.metaclass屬於超程式設計(metaprogramming)的範疇,所謂超程式設計就是讓程式來寫
(generate/modify)程式,這通常依賴於語言及其執行時系統的動態特性(其實像C
這樣的語言也可以進行超程式設計)。正如樓主所說,超程式設計的一個用途就是“可以用另
外的函式程式碼生成,而無需每次手動編寫“,在python中我們可以做得更多。
2.對於python而言,metaclass使程式設計師可以干涉class的建立過程,並可以在任何
時候修改這樣的class(包括修改metaclass),由於class的意義是為instance集合
持有“方法”,所以修改了一個class就等於修改了所有這些instance的行為,這是
很好的service。
3.注意metaclass的__new__和__init__的區別。
class DynamicMethod(type):
def __new__(cls, name, bases, dct): # cls=DynamicMethod
def __init__(cls, name, bases, dct): # cls=你建立的class物件
這意味著在__new__中我們通常只是修改dct,但是在__init__中,我們可以直接修
改建立好的類,所以我認為這兩個介面的主要區別有2點:1)呼叫時機不同(用處請
發散思維);2)__init__比__new__更有用,我在實際專案中一般都是用__init__的。
4.在python中我們為什麼要修改class?那當然是為了改變它的行為,或者為了創
建出獨一無二的類。實際中常常需要為class動態新增方法。比如一個數據庫表A有
欄位name, address等,表B有name, phone等,你希望A的模型類有
find_by_address、find_by_name_and_address等方法,希望B的模型類有
find_by_name、find_by_phone等方法,但是又不想手寫這些方法(其實不可能手
寫,因為這種組合太多了),這時你可以在A、B共同的metaclass中定義一個自動添
加方法的子程式,當然你也可以重寫__getattr__之類的介面來hook所有
find_by_XXX函式呼叫,這就是時間換空間了,想象你要執行find_by_XXX一百萬
次。也可以比較一下在c++/java中如何應對這種需求。
5.python的成功之處在於它無處不在的namespace(就是那個__dict__,其意義可以
參考SICP第一章的environment模型,對計算理論感興趣的也可以用lambda演算來
解釋),而且函式又是first class物件,又強化了interface的使用。我們知道了
metaclass->class->instance的關係,又知道了物件的方法是放在類裡的(請詳細
考察python查詢一個方法的流程),那麼用python實現各種設計模式就比較簡單了。
6.metaclass不會使程式變晦澀,前提是瞭解了metaclass的固有存在,許多教程的
問題就在於它沒有告訴讀者metaclass的存在,而是藉助某些其他語言(比如c++)的
類模型來講解python。在ruby的型別系統中metaclass是無限的,metaclass也有自
己的metaclass(你可以稱之為metametaclass、metametametaclass等等),ruby
善於實現DSL和語法分析器也部分得益於此。
嶽同學:
不能說__init__比__new__更有用吧。我覺得要看場合。畢竟__new__能做到比
__init__更多的事情。比如有時候想改生成的型別名字,或者改型別的父類。:)
不過的確大多數場合用__init__就夠用了。+1
在__init__中控制類生成的過程有一點要注意:在__init__()的最後一個參
數(attrs)中,對於類中定義的函式型別的屬性,比如:
def abc(self):
pass
仍然具有以下的key->value形式:
"abc":<function object>
但是在生成的類中,"abc"對應的屬性已經從一個function變成了一個unbind
method:
self.abc --> unbind method
不過實際使用中影響不大。
七 其他
1. 哪些專案有用metaclass:
據我所知就是django中的資料庫部分的很多都使用metaclass來實現可描述性的
還有google app engine的程式碼裡面也有使用
yaml中的序列化部分程式碼也有使用
more...
2. 參考資料:
3. 以上都是個人的觀點和總結而已,歡迎拍磚。