Python高階特性(3): Classes和Metaclasses
類和物件
類和函式一樣都是Python中的物件。當一個類定義完成之後,Python將建立一個“類物件”並將其賦值給一個同名變數。類是type型別的物件(是不是有點拗口?)。
類物件是可呼叫的(callable,實現了 __call__方法),並且呼叫它能夠建立類的物件。你可以將類當做其他物件那麼處理。例如,你能夠給它們的屬性賦值,你能夠將它們賦值給一個變數,你可以在任何可呼叫物件能夠用的地方使用它們,比如在一個map中。事實上當你在使用map(str, [1,2,3])的時候,是將一個整數型別的list轉換為字串型別的list,因為str是一個類。可以看看下面的程式碼:
12345678910111213141516171819202122232425 | >>>classC(object):...def __init__(self,s):...prints...>>>myclass=C>>>type(C)<type'type'>>>>type(myclass |
正因如此,Python中的“class”關鍵字不像其他語言(例如C++)那樣必須出現在程式碼main scope中。在Python中,它能夠在一個函式中嵌套出現,舉個例子,我們能夠這樣在函式執行的過程中動態的建立類。看程式碼:
12345678910111213141516171819 | >>>def make_class(class_name):...classC(object):...def print_class_name(self):...print class_name...C.__name__=class_name...returnC...>>>C1,C2=map(make_class,["C1","C2"])>>>c1,c2=C1(),C2()>>>c1.print_class_name()C1>>>c2.print_class_name()C2>>>type(c1)<class'__main__.C1'>>>>type(c2)<class'__main__.C2'>>>>c1.print_class_name.__closure__(<cell at0x10ab6dbe8:str objectat0x10ab71530>,) |
請注意,在這裡通過make_class建立的兩個類是不同的物件,因此通過它們建立的物件就不屬於同一個型別。正如我們在裝飾器中做的那樣,我們在類被建立之後手動設定了類名。同樣也請注意所建立類的print_class_name方法在一個closure cell中捕捉到了類的closure和class_name。如果你對closure的概念還不是很清楚,那麼最好去看看前篇,複習一下closures和decorators相關的內容。
Metaclasses
如果類是能夠製造物件的物件,那製造類的物件又該叫做什麼呢(相信我,這並不是一個先有雞還是先有蛋的問題)?答案是元類(Metaclasses)。大部分常見的基礎元類都是type。當輸入一個引數時,type將簡單的返回輸入物件的型別,這就不涉及元類。然而當輸入三個引數時,type將扮演元類的角色,基於輸入引數建立一個類並返回。輸入引數相當簡單:類名,父類及其引數的字典。後面兩者可以為空,來看一個例子:
123456 | >>>MyClass=type("MyClass",(object,),{"my_attribute":0})>>>type(MyClass)<type'type'>>>>o=MyClass()>>>o.my_attribute0 |
特別注意第二個引數是一個tuple(語法看起來很奇怪,以逗號結尾)。如果你需要在類中安排一個方法,那麼建立一個函式並且將其以屬性的方式傳遞作為第三個引數,像這樣:
123456789 | >>>def myclass_init(self,my_attr):...self.my_attribute=my_attr...>>>MyClass=type("MyClass",(object,),{"my_attribute":0,"__init__":myclass_init})>>>o=MyClass("Test")>>>o.my_attribute'Test'>>>o.__init__<bound method MyClass.myclass_init of<__main__.MyClass objectat0x10ab72150>> |
我們可以通過一個可呼叫物件(函式或是類)來自定義元類,這個物件需要三個輸入引數並返回一個物件。這樣一個元類在一個類上實現只要定義了它的__metaclass__屬性。第一個例子,讓我們做一些有趣的事情看看我們能夠用元類做些什麼:
12345678910 | >>>def mymetaclass(name,parents,attributes):...return"Hello"...>>>classC(object):...__metaclass__=mymetaclass...>>>printCHello>>>type(C)<type'str'> |
請注意以上的程式碼,C只是簡單地將一個變數引用指向了字串“Hello”。當然了,沒人會在實際中寫這樣的程式碼,這只是為了演示元類的用法而舉的一個簡單例子。接下來我們來做一些更有用的操作。在本系列的第二部分我們曾看到如何使用裝飾器類來記錄目標類每個方法的輸出,現在我們來做同樣的事情,不過這一次我們使用元類。我們借用之前的裝飾器定義:
1234567891011121314151617181920212223 | def log_everything_metaclass(class_name,parents,attributes):print"Creating class",class_namemyattributes={}forname,attr inattributes.items():myattributes[name]=attrifhasattr(attr,'__call__'):myattributes[name]=logged("%b%d%Y-%H:%M:%S",class_name+".")(attr)returntype(class_name,parents,myattributes)classC(object):__metaclass__=log_everything_metaclassdef __init__(self,x):self.x=xdef print_x(self):print self.x# Usage:print"Starting objectcreation"c=C("Test")c.print_x() |
12345678 | # Output:Creating classCStarting objectcreation-Running'C.__init__' on Aug 05 2013 - 13:50:58-Finished'C.__init__', execution time = 0.000s-Running'C.print_x' on Aug 05 2013 - 13:50:58Test-Finished'C.print_x', execution time = 0.000s |
如你所見,類裝飾器與元類有著很多共同點。事實上,任何能夠用類裝飾器完成的功能都能夠用元類來實現。類裝飾器有著很簡單的語法結構易於閱讀,所以提倡使用。但就元類而言,它能夠做的更多,因為它在類被建立之前就運行了,而類裝飾器則是在類建立之後才執行的。記住這點,讓我們來同時執行一下兩者,請注意執行的先後順序: