python 元類


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在加載的時候掃過整個文件.遇到類定義的時候如下執行:

  1. 解析類中的元素, 創建類變量與方法, 並加載到類空間中. 類的基類信息, 元類等信息.
  2. 找到該類的元類,然后調用元類的__new__方法,參數是(類名where,類基類bases,類空間dict)
  3. 元類的__new__最終一定會調用內置類型type.__new__
  4. type.__new__會調用元類的__init__創建出一個類對象放在內存中.至此類對象已經加載完成了.

執行過程

現在我們來看看執行的

t = Test(1) print(t) 
  1. Test是什么?從語法層面上,他是一個類.但是在執行過程中,經過上面的加載步驟,它是一個生成的實例,所以Test()會調用元類的__call__方法.
  2. 元類一定又得陷入type.__call__方法.
  3. type.__call__方法調用類__new__方法.
  4. 類的__new__方法一定又陷入object.__new__
  5. 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關鍵詞.
在創建一個對象的時候,解釋器做了如下工作:

  1. It creates a new object.
  2. It sets the constructor property of the object to Vehicle.
  3. It sets up the object to delegate to Vehicle.prototype.
  4. 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里非常聰明的做法也是整個的基石.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM