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里非常聰明的做法也是整個的基石.
