萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> 編程語言綜合 >> Python中的Classes和Metaclasses詳解

Python中的Classes和Metaclasses詳解

 類和對象

類和函數一樣都是Python中的對象。當一個類定義完成之後,Python將創建一個“類對象”並將其賦值給一個同名變量。類是type類型的對象(是不是有點拗口?)。

類對象是可調用的(callable,實現了 __call__方法),並且調用它能夠創建類的對象。你可以將類當做其他對象那麼處理。例如,你能夠給它們的屬性賦值,你能夠將它們賦值給一個變量,你可以在任何可調用對象能夠用的地方使用它們,比如在一個map中。事實上當你在使用map(str, [1,2,3])的時候,是將一個整數類型的list轉換為字符串類型的list,因為str是一個類。可以看看下面的代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 >>> class C(object): ... def __init__(self, s): ... print s ... >>> myclass = C >>> type(C) <type 'type'> >>> type(myclass) <type 'type'> >>> myclass(2) 2 <__main__.C object at 0x10e2bea50> >>> map(myclass, [1,2,3]) 1 2 3 [<__main__.C object at 0x10e2be9d0>, <__main__.C object at 0x10e2bead0>, <__main__.C object at 0x10e2beb10>] >>> map(C, [1,2,3]) 1 2 3 [<__main__.C object at 0x10e2be950>, <__main__.C object at 0x10e2beb50>, <__main__.C object at 0x10e2beb90>] >>> C.test_attribute = True >>> myclass.test_attribute True

正因如此,Python中的“class”關鍵字不像其他語言(例如C++)那樣必須出現在代碼main scope中。在Python中,它能夠在一個函數中嵌套出現,舉個例子,我們能夠這樣在函數運行的過程中動態的創建類。看代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 >>> def make_class(class_name): ... class C(object): ... def print_class_name(self): ... print class_name ... C.__name__ = class_name ... return C ... >>> 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 at 0x10ab6dbe8: str object at 0x10ab71530>,)

請注意,在這裡通過make_class創建的兩個類是不同的對象,因此通過它們創建的對象就不屬於同一個類型。正如我們在裝飾器中做的那樣,我們在類被創建之後手動設置了類名。同樣也請注意所創建類的print_class_name方法在一個closure cell中捕捉到了類的closure和class_name。如果你對closure的概念還不是很清楚,那麼最好去看看前篇,復習一下closures和decorators相關的內容。
Metaclasses

如果類是能夠制造對象的對象,那制造類的對象又該叫做什麼呢(相信我,這並不是一個先有雞還是先有蛋的問題)?答案是元類(Metaclasses)。大部分常見的基礎元類都是type。當輸入一個參數時,type將簡單的返回輸入對象的類型,這就不涉及元類。然而當輸入三個參數時,type將扮演元類的角色,基於輸入參數創建一個類並返回。輸入參數相當簡單:類名,父類及其參數的字典。後面兩者可以為空,來看一個例子:

1 2 3 4 5 6 >>> MyClass = type("MyClass", (object,), {"my_attribute": 0}) >>> type(MyClass) <type 'type'> >>> o = MyClass() >>> o.my_attribute 0

特別注意第二個參數是一個tuple(語法看起來很奇怪,以逗號結尾)。如果你需要在類中安排一個方法,那麼創建一個函數並且將其以屬性的方式傳遞作為第三個參數,像這樣:

1 2 3 4 5 6 7 8 9 >>> 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 object at 0x10ab72150>>

我們可以通過一個可調用對象(函數或是類)來自定義元類,這個對象需要三個輸入參數並返回一個對象。這樣一個元類在一個類上實現只要定義了它的__metaclass__屬性。第一個例子,讓我們做一些有趣的事情看看我們能夠用元類做些什麼:

1 2 3 4 5 6 7 8 9 10 >>> def mymetaclass(name, parents, attributes): ... return "Hello" ... >>> class C(object): ... __metaclass__ = mymetaclass ... >>> print C Hello >>> type(C) <type 'str'>

請注意以上的代碼,C只是簡單地將一個變量引用指向了字符串“Hello”。當然了,沒人會在實際中寫這樣的代碼,這只是為了演示元類的用法而舉的一個簡單例子。接下來我們來做一些更有用的操作。在本系列的第二部分我們曾看到如何使用裝飾器類來記錄目標類每個方法的輸出,現在我們來做同樣的事情,不過這一次我們使用元類。我們借用之前的裝飾器定義:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def log_everything_metaclass(class_name, parents, attributes): print "Creating class", class_name myattributes = {} for name, attr in attributes.items(): myattributes[name] = attr if hasattr(attr, '__call__'): myattributes[name] = logged("%b %d %Y - %H:%M:%S", class_name + ".")(attr) return type(class_name, parents, myattributes)
copyright © 萬盛學電腦網 all rights reserved