標籤:檢查 類載入 ret 做了 meta *args type erro python3.6
python 元類
之前想清楚了寫到了筆記中,最近看到python3.6又出了個__init_subclass__
,之前的東西又全忘了.這次在總結一下.
new: 結合javascript的原型鏈體會一下動態語言一切皆對象的思想.
以一個實用的執行個體
#!/usr/bin/env pythonclass 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運行到 Testset 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(第一步建立出來的Null 物件)
,這個時候函數的this參數就會指向這個新的對象,這樣就會擁有執行個體變數(每個人都不一樣的)
所以最合理的類比類實現是這個樣子的
// Class definition / constructorvar Vehicle = function Vehicle(color) { // Initialization this.color = color;}// Instance methodsVehicle.prototype = { go: function go() { return "Vroom!"; }}
prototype裡的東西所有的變數都共用一份,在執行個體裡找不到就會向上尋找,而function這個構造器裡的this因為指向的是一個{}每次執行都會重新賦值一遍,而且會屏蔽在prototype上設定的屬性.
this和prototype是js裡非常聰明的做法也是整個的基石.
python 元類