2.1.5 Python元類深入理解(metaclass)

點擊跳轉Python筆記總目錄python

本節目錄

1、建立類的執行流程

2、元類的認識

3、元類的示例


1、建立類的執行流程

類建立過程當中所須要的信息

一個python類在建立過程當中,須要獲取兩種類型的信息,即:動態元信息,與靜態元信息。 所謂動態元信息,是指那些隨着類的變化會改變的信息。好比:類名稱,類基類,類屬性。 而所謂靜態元信息,是指與類的種類沒有關係的靜態信息,這裏主要指的是類建立的方式和過程。 我想,對於動態元信息,你們是再熟悉不過了。 而對於靜態元信息,其概念顯得晦澀難懂。然而它其實有一個響亮的名字:元類。bash

元類

咱們都知道,python 到處皆對象。所以,在python中,類(class)自己也是一個對象(object),而元類則是類的類型。在個人文章深刻理解python之對象系統中,我也曾經梳理過,普通對象(PyXXX_Object),類對象(PyXXX_Type),基類對象(PyBaseObject_Type)以及元類(PyType_TYpe)之間的關係。 函數

在這裏插入圖片描述
若是沒有指定,那麼全部類的類型(ob_type)都指向type對象(PyType_Type)。也就是說全部類型對象的默認元類就是這個type。

咱們前面提到類的靜態元信息,即爲元類。也就是說,元類描述了類的建立方式和過程,那麼它是如何作到的呢?這裏咱們閱讀以下python源碼:post

static PyObject *build_class(PyObject *methods, PyObject *bases, PyObject *name)
{
    //methods:類屬性列表
    //bases:基類元組
    //name:類名稱
    PyObject *metaclass = NULL;//元類
    ...
    //從methods裏尋找用戶自定義的metaclass,若是找不到,則使用默認的metaclass
    ...
    result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods,
                                      NULL);
}

PyObject_CallFunctionObjArgs(PyObject *callable, ...)
{
    ...
    tmp = PyObject_Call(callable, args, NULL);
    ...
    return tmp;
}

PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
    //func=metaclass
    //args=[methods,bases,name]
    ...     
    call = func->ob_type->tp_call;
    ...
    result = (*call)(func, arg, kw);
    ...
}

複製代碼

build_class 函數是建立類對象過程當中的一個核心函數,build_class函數傳入的參數是類的動態元信息:屬性,類基類,類名。而build_class作的第一件事是,肯定類的元類(metaclass)。在找到了元類之後,實際上python底層在這裏對元類對象執行了一個 調用(call)的動做,就像調用一個函數對象那樣。假設某個類對象的元類對象是metaclass。那麼在類建立過程當中圍繞元類的核心操做以下:ui

class=metaclass(metaclass,methods,bases,name)
複製代碼

由此咱們能夠知道,所謂的類對象的靜態元信息,也就是類對象建立的過程與方式,都封裝在了某個callable的metaclass對象的函數體內。spa

與之相對比的,建立某個普通對象的過程也就是對於一個類對象的調用過程:設計

obj=class(class,...);
複製代碼

class 是metaclass的實例,因此調用metaclass獲得class。而obj是class的實例,因此調用class獲得obj。在這裏,python 「 到處皆對象」的設計哲學得以很好的體現。咱們由此能夠作個總結:在python中,類是經過 「調用(call)「的方式建立一個對象。code

類建立過程的原理分析

本小節咱們經過一個具體的例子去分析,類建立過程當中的具體步驟。 實例代碼:cdn

class meta(type):
    '''元類'''
    def __new__(metacls,name,bases,methods):
        print metacls,name
        return type.__new__(metacls,name,bases,methods)

    def __init__(cls,name,bases,methods):
        print cls,name

class T(object):
    '''類'''
    __metaclass__=meta #指定元類
    a=1
    def b():
        pass
    def c():
        pass
複製代碼

首先咱們經過compile 函數和dis模塊去獲取類型T定義過程對應的指令序列:對象

22 LOAD_CONST               2 ('T')
25 LOAD_NAME                2 (object)
28 BUILD_TUPLE              1
31 LOAD_CONST               3 (<code object T at 0x1013eac30, file "", line 18>)
34 MAKE_FUNCTION            0
37 CALL_FUNCTION            0
40 BUILD_CLASS         
41 STORE_NAME               3 (T)
44 LOAD_CONST               4 (None)
47 RETURN_VALUE

複製代碼
  • LOAD_CONST 是向當前解釋器執行棧內壓入類名T。

  • LOAD_NAME 和BUILD_TUPLE指令實際上完成了對於類基類元組(bases)的準備,BUILD_TUPLE 指令執行完成後,當前解釋器執行棧上已經存入了函數名和基類元組。

  • LOAD_CONST,MAKE_FUNCTION,CALL_FUNCTION 三條指令,實際上完成了類屬性字典的定義與收集。

  • LOAD_CONST 加載的是類中屬性定義語句所對應的PyCodeObject。

  • MAKE_FUNCTION 是利用類中屬性定義語句的PyCodeObject 創造一個PyFunctionObject

  • CALL_FUNCTION 則是使解釋器執行類中屬性定義語句的指令序列,從而完成類屬性的定義過程。

  • BUILD_CLASS 顯然是建立一個類的核心指令,其實際上就是調用了前文所述的build_class函數。

  • STORE_NAME 指令將通過BUILD_CLASS 指令以後建立的類對象,存入名字T所對應的空間,至此,整個類的建立過程結束。

接下來,咱們從新回到整個類建立過程的核心build_class 函數。

解釋器在處理咱們的實例代碼過程當中,顯然有:

//T_pro_dict={"__metaclass__"=meta,"a":1,"b":...}
//T_bases=[object]
build_class(T_pro_dict,T_bases,"T");
複製代碼

因此在PyObject_Call中有:

PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
    {
        //func=meta
        //args=[T_pro_dict,T_bases,"T"]
        ...     
        call = func->ob_type->tp_call;
        ...
        result = (*call)(func, arg, kw);
        ...
    }

複製代碼

這裏咱們思考,meta->ob_type->tp_call是什麼?meta是咱們定義的元類對象,做爲一個類型對象自己,其ob_type 是指向type對象的,所以meta->ob_type->tp_call 指向的是type對象的tp_call 成員,也就是:PyType_type->tp_call。 繼續追蹤PyType_type->tp_call的源碼:

static PyObject *type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    //type = meta
    //args=[T_pro_dict,T_bases,"T"]
    PyObject *obj;
    ...
    obj = type->tp_new(type, args, kwds);
    ...
    if(type->tp_init && type != &PyType_Type)
        type->tp_init(obj, args, kwds);
    ...
    return obj;
}

複製代碼

在pyType_type->tp_call 所對應的函數裏,首先調用了metaclass 的new函數,又因爲當前元類不是默認的type類型,所以也會執行metaclass的init函數。落實到在咱們的示例代碼中,顯然有以下邏輯得以執行:

class_obj=meta.__new__(meta,"T",T_bases,T_pro_dict)
meta.__init__(class_obj,"T",T_bases,T_pro_dict)
複製代碼

從實例代碼的執行結果也能夠獲得印證:

<class '__main__.meta'> T
<class '__main__.T'> T
複製代碼

經過名字咱們不難猜想,這裏的_new_ 至關於python 類對象的構造函數,實際負責了類對象內存的申請,動態元信息的填充等工做。而_init_ 則是一個可選的初始化函數,由元類的定製者設計其中的內容。 在咱們的實例中,meta._new函數調用了type._new函數完成了類對象的建立,type.new 在python源碼中對應的的是type_new函數。

static PyObject *type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
    PyObject *name, *bases, *dict;
    ...
    // 獲取目標類的動態元信息:類名,基類元組,屬性字典
    PyArg_ParseTupleAndKeywords(args, kwds, "SO!O!:type", kwlist,
                                 &name,
                                 &PyTuple_Type, &bases,
                                 &PyDict_Type, &dict);

    ...
    // 類對象的內存申請
    type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
    
    /*類對象的動態元信息填充*/
    //類名
    type->tp_name = PyString_AS_STRING(name); 
    ...
    //基類
    type->tp_bases = bases;
    ...
    //屬性
    type->tp_dict = dict = PyDict_Copy(dict);
    ...
    // 類型的其餘信息的初始化
    PyType_Ready(type);
    ...
    return (PyObject *)type;
}

複製代碼

在type_new函數的中,咱們能夠清楚的看到類對象內存申請,動態元信息填充的具體實現。 至此,一個元類建立一個類的主幹過程,就梳理完畢了。

2、元類的認識

什麼是元類呢?在Python3中繼承type的就是元類

3、元類的示例

方式一:

class MyType(type):
    '''繼承type的就是元類'''
    def __init__(self,*args,**kwargs):
        print("MyType建立的對象",self)   #Foo
        super(MyType,self).__init__(*args,**kwargs)

    def __call__(self, *args, **kwargs):
        obj = super(MyType,self).__call__(*args,**kwargs)
        print("類建立對象",self,obj)   #Foo

class Foo(object,metaclass=MyType): # 對象加括號會去執行__call__方法,__call__方法裏面繼承了type的__call__方法type的__call__方法裏面會先執行__new__方法,再去執行__init__方法。
                                      因此,Foo就是用type建立出來的
    user = "haiyan"
    age = 18

obj = Foo()
複製代碼

方式二:

class MyType(type):
    def __init__(self, *args, **kwargs):
        print("ssss")
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj
#對象加括號就會去執行__call__方法
class Foo(MyType('Zcc', (object,), {})):  #MyType('Zcc', (object,), {})至關於class Zcc(object):pass,也就是建立了一個Zcc的類
    user = 'haiyan'
    age = 18

obj = Foo()
複製代碼

方式三:

class MyType(type):
    def __init__(self, *args, **kwargs):
        print("ssss")
        super(MyType, self).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        v = dir(cls)
        obj = super(MyType, cls).__call__(*args, **kwargs)
        return obj
#對象加括號就會去執行__call__方法

def with_metaclass(arg,base):
    print("類對象",MyType('Zcc', (base,), {}))
    return arg('Zcc', (base,), {})  #返回一個類對象 <class '__main__.Zcc'>

class Foo(with_metaclass(MyType,object)):  #MyType('Zcc', (object,), {})至關於class Zcc(object):pass,也就是建立了一個Zcc的類
    user = 'haiyan'
    age = 18

obj = Foo()
複製代碼
class ASD(type):
    pass

qqq = ASD("qwe", (object,), {})  #用ASD這個元類建立了一個(qwe,而且繼承object類的)類

# class ASD(qwe):
# pass
obj = qqq()
# 能建立類的是元類
# 能建立對象的是類
print(obj)  #<__main__.qwe object at 0x00000000024FFBA8>
print(obj.__class__)  #<class '__main__.qwe'>
print(obj.__class__.__class__)  #<class '__main__.ASD'>
print(obj.__class__.__class__.__class__)  #<class 'type'>
print(obj.__class__.__class__.__class__.__class__)  #<class 'type'>
複製代碼
相關文章
相關標籤/搜索