python 元類
之前想清楚了寫到了筆記中,最近看到python3.6又出了個__init_subclass__
,之前的東西又全忘了.這次在總結一下.
new: 結合javascript的原型鏈體會一下動態語言一切皆對象的思想.
以一個實用的實例
#!/usr/bin/env python class Type(object): print("運行到", "Type") def __init__(self, type_): print("set type", type_) self.type_class = type_ def vaild(self, value): return isinstance(value, self.type_class) class TypeCheckMeta(type): print("運行到", "TypeCheckMeta") def __new__(cls, name, bases, dict): print("元類 __new__") inst = super(TypeCheckMeta, cls).__new__(cls, name, bases, dict) inst._fileds = {} for k, v in dict.items(): if isinstance(v, Type): inst._fileds.setdefault(k, v) return inst def __init__(cls, *args, **kwargs): print("元類 __init__") super(TypeCheckMeta, cls).__init__(*args, **kwargs) def __call__(self, *args, **kwargs): print("元類 __call__") return super(TypeCheckMeta, self).__call__(*args, **kwargs) class Test(metaclass=TypeCheckMeta): print("運行到", "Test") name = Type(str) age = Type(int) def __new__(cls, *args, **kwargs): print("類 __new__") print(args, kwargs) return super(Test, cls).__new__(cls) def __setattr__(self, key, value): print("類 __setattr__") if key in self._fileds: if not self._fileds[key].vaild(value): raise TypeError("invaild...") super(Test, self).__setattr__(key, value) def __init__(self, a): print("類 __init__") def __call__(self, *args, **kwargs): print("類 __call__") t = Test(1) print(t)
場景就是需要你對變量做強制性檢查.
加載過程
注釋掉最后兩行代碼,會發現如下輸出
運行到 Type 運行到 TypeCheckMeta 運行到 Test set type <class 'str'> set type <class 'int'> 元類 __new__ 元類 __init__
首先,Python在加載的時候掃過整個文件.遇到類定義的時候如下執行:
- 解析類中的元素, 創建類變量與方法, 並加載到類空間中. 類的基類信息, 元類等信息.
- 找到該類的元類,然后調用元類的
__new__
方法,參數是(類名where,類基類bases,類空間dict)
- 元類的
__new__
最終一定會調用內置類型type.__new__
type.__new__
會調用元類的__init__
創建出一個類對象放在內存中.至此類對象已經加載完成了.
執行過程
現在我們來看看執行的
t = Test(1) print(t)
Test
是什么?從語法層面上,他是一個類.但是在執行過程中,經過上面的加載步驟,它是一個生成的實例,所以Test()
會調用元類的__call__
方法.- 元類一定又得陷入
type.__call__
方法. type.__call__
方法調用類__new__
方法.- 類的
__new__
方法一定又陷入object.__new__
object.__new__
調用類的__init__
方法,最終一個實例被創建出來了.
新的方法__init_subclass__(self, k=...)
觸發時機: 子類加載時觸發.
具體的: 加載子類時發現父類定義了__init_subclass__
方法,那么在元類__new__
之后__init__
之前調用這個__init_subclass__
.(實現應該不是這樣子的,應該是基於回調.比如在type這個元元元類基礎上檢測調用__init_subclass__).
這樣就不需要寫元類就可以修飾子類了.
其傳參數方式並沒有什么魔法,
class SubClass(Father, param="haha"): pass
在傳遞給__new__
的時候給現在要多一個位置參數
def __new__(cls, name, bases, dict, **kwargs): pass
這樣子__init_subclass__
也可以獲取到了.
動態語言–一切都是對象
function People(name) { this.name = name } p = new People("Irn")
不了解js原型鏈的同學會很疑惑這種寫法.很明顯通過關鍵詞function
表明了People是個函數
,那么,new 函數
算什么語法?
實際上js里的函數可不僅僅是一個c語言層面的函數,他是一個完整的實例,是Function創建出來的實例.
f = new Function('name', 'return alert("hello, " + name + "!");');
這樣我們就有一個constructor
的對象,他們可以使用new
關鍵詞.
在創建一個對象的時候,解釋器做了如下工作:
- It creates a new object.
- It sets the constructor property of the object to Vehicle.
- It sets up the object to delegate to Vehicle.prototype.
- It calls Vehicle() in the context of the new object.
具體的看參考網上的說明!
其中在設置prototype的時候完成了類變量,類函數的繼承.
最后一步調用函數的.prototype.call(第一步創建出來的空對象)
,這個時候函數的this參數就會指向這個新的對象,這樣就會擁有實例變量(每個人都不一樣的)
所以最合理的模擬類實現是這個樣子的
// Class definition / constructor var Vehicle = function Vehicle(color) { // Initialization this.color = color; } // Instance methods Vehicle.prototype = { go: function go() { return "Vroom!"; } }
prototype里的東西所有的變量都共享一份,在實例里找不到就會向上查找,而function這個構造器里的this因為指向的是一個{}每次執行都會重新賦值一遍,而且會屏蔽在prototype上設置的屬性.
this和prototype是js里非常聰明的做法也是整個的基石.