《深度剖析CPython解釋器》2. 解密PyObject、PyVarObject、PyTypeObject在Python對象體系中所代表的含義,用CPython來總結Python中type和object之間的關系


楔子

我們在上一篇中說到了,面向對象理論中"類"和"對象"這兩個概念在Python內部都是通過"對象"實現的。"類"是一種對象,稱為"類型對象","類"實例化得到的也是"對象",稱為"實例對象"。

並且根據對象的不同特點還可以進一步分類:

  • 可變對象:對象創建之后可以本地修改;
  • 不可變對象:對象創建之后不可以本地修改;
  • 定長對象:對象所占用的內存大小固定;
  • 不定長對象:對象所占用的內存大小不固定;

但是"對象"在Python的底層是如何實現的呢?我們知道標准的Python解釋器是C語言實現的CPython,但C並不是一個面向對象的語言,那么它是如何實現Python中的面向對象的呢?

首先對於人的思維來說,對象是一個比較形象的概念,但對於計算機來說,對象卻是一個抽象的概念。它並不能理解這是一個整數,那是一個字符串,計算機所知道的一切都是字節。通常的說法是:對象是數據以及基於這些數據的操作的集合。在計算機中,一個對象實際上就是一片被分配的內存空間,這些內存可能是連續的,也可能是離散的。

而Python中的任何對象在C中都對應一個結構體實例,在Python中創建一個對象,等價於在C中創建一個結構體實例。所以Python中的對象本質上就是C中malloc函數為結構體實例在堆區申請的一塊內存。

下面我們就來分析一下Python中的對象在C中是如何實現的,究竟生得一副什么模樣,是三頭六臂還是烈焰紅唇。而第一步,就是下面要介紹的PyObject。

實現對象機制的基石--PyObject

Python中一切皆對象,而所有的對象都擁有一些共同的信息(也叫頭部信息),這些信息就在PyObject中,PyObject是Python整個對象機制的核心,我們來看看它的定義:

//Include/object.h
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

我們看到以上便是PyObject的內部信息,我們先來看看_PyObject_HEAD_EXTRA,這是一個宏,如果將其展開的話:

#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;

#define _PyObject_EXTRA_INIT 0, 0,

#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif

那么這個宏是做什么的呢?這個宏是用來實現一個名叫refchain的"雙向鏈表"的,Python會將程序中創建的所有對象都放入到這個雙向鏈表中,用於跟蹤所有活躍的堆對像。每一個對象都指向了它的前一個對象和后一個對象,如果是第一個對象,那么它的前繼節點為NULL;如果是最后一個節點,那么它的后繼節點為NULL。不過這個宏僅僅是在debug下有用,所以我們目前不需要管這個宏。

我們的重心是PyObject中的這個宏下面的兩位老鐵:ob_refcnt和ob_type。

ob_refcnt:引用計數

ob_refcnt表示對象的引用計數,當一個對象被引用時,那么ob_refcnt會自增1;引用解除時,ob_refcnt自減1。而一旦對象的引用計數為0時,那么這個對象就會被回收。

那么在哪些情況下,引用計數會加1呢?哪些情況下,引用計數會減1呢?

導致引用計數加1的情況:

  • 對象被創建:比如name = "古明地覺", 此時對象就是"古明地覺"這個字符串, 創建成功時它的引用計數為1
  • 變量傳遞使得對象被新的變量引用:比如Name = name
  • 引用該對象的某個變量作為參數傳到一個函數或者類中:比如func(name)
  • 引用該對象的某個變量作為元組、列表、集合等容器的一個元素:比如lst = [name]

導致引用計數減1的情況:

  • 引用該對象的變量被顯示的銷毀:del name
  • 對象的引用指向了別的對象:name = "椎名真白"
  • 引用該對象的變量離開了它的作用域,比如函數的局部變量在函數執行完畢的時候會被銷毀
  • 引用該對象的變量所在的容器被銷毀,或者被從容器里面刪除

所以我們使用del刪除一個對象,並不是刪除這個對象,我們沒有這個權力,del只是使對象的引用計數減一,至於到底刪不刪是解釋器判斷對象引用計數是否為0決定的。為0就刪,不為0就不刪,就這么簡單。

而ob_refcnt的類型是Py_ssize_t,在64位機器上直接把這個類型看成long即可(話說這都2020年了,不會還有人用32位機器吧),因此一個對象的引用計數不能超過long所表示的最大范圍。但是顯然,如果不是吃飽了撐的寫惡意代碼,是不可能超過這個范圍的。

ob_type:類型指針

我們說一個對象是有類型的,類型對象描述實例對象的數據和行為,而ob_type存儲的便是對應類型對象的指針,所以類型對象在底層對應的是struct _typeobject實例。從這里我們可以看出,所有的類型對象在底層都是由同一個結構體實例化得到的,因為PyObject是所有的對象共有的,它們的ob_type指向的都是struct _typeobject。

所以不同的實例對象對應不同的結構體,但是類型對象對應的都是同一個結構體。

因此我們看到PyObject的定義非常簡單,就是一個引用計數和一個類型指針,所以Python中的任意對象都必有:引用計數和類型這兩個屬性。

實現變長對象的基石--PyVarObject

我們說PyObject是所有對象的核心,它包含了所有對象都共有的信息,但是還有那么一個屬性雖然不是每個對象都有,但至少有一大半的對象會有,能猜到是什么嗎?

我們說Python中的對象根據所占的內存是否固定可以分為定長對象和變長對象,而變長對象顯然有一個長度的概念,比如字符串、列表、元組等等,即便是相同的實例對象,但是長度不同,所占的內存也是不同的。比如:字符串內部有多少個字符、元組、列表內部有多少個元素,顯然這里的多少也是Python中很多對象的共有特征,雖然不像引用計數和類型那樣是每個對象都必有的,但也是相當大一部分對象所具有的。

所以針對變長對象,Python底層也提供了一個結構體,因為Python很多都是變長對象。

//Include/object.h
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

所以我們看到PyVarObject實際上是PyObject的一個擴展,它在PyObject的基礎上提供了一個ob_size字段,用於記錄內部的元素個數。比如列表,列表(PyListObject實例)中的ob_size維護的就是列表的元素個數,插入一個元素,ob_size會加1,刪除一個元素,ob_size會減1。所以我們使用len獲取列表的元素個數是一個時間復雜度為O(1)的操作,因為ob_size是時刻都和內部的元素個數保持一致,使用len獲取元素個數的時候會直接訪問ob_size。

因此在Python中,所有的變長對象都擁有PyVarObject,而所有的對象都擁有PyObject,這就使得在Python中,對"對象"的引用變得非常統一,我們只需要一個PyObject *就可以引用任意一個對象,而不需要管這個對象實際是一個什么對象。所以在Python中,所有的變量、以及容器內部的元素,本質上都是一個PyObject *。

由於PyObject和PyVarObject要經常被使用,所以Python提供了兩個宏,方便定義。

#define PyObject_HEAD          PyObject ob_base;
#define PyObject_VAR_HEAD      PyVarObject ob_base;

比如定長對象浮點數,在底層對應的結構體為PyFloatObject,只需在頭部PyObject的基礎上再加上一個double即可。

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

而對於變長對象列表,在底層對應的結構體是PyListObject,所以它需要在PyVarObject的基礎上再加上一個指向數組的二級指針和一個容量即可。

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

這上面的每一個成員都代表什么,我們之前已經分析過了。ob_item就是指向指針數組的二級指針,而allocated表示已經分配的容量,一旦添加元素的時候發現ob_size自增1之后會大於allocated,那么解釋器就會對ob_item指向的指針數組進行擴容了。更准確的說,是申請一個容量更大數組,然后將原來指向的指針數組內部的元素按照順序一個一個地拷貝到新的數組里面去,並讓ob_item指向新的數組,這一點在分析PyListObject的時候會細說。所以我們看到列表在添加元素的時候,地址是不會改變的,即使容量不夠了也沒有關系,直接讓ob_item指向新的數組就好了,至於PyListObject對象本身的地址是不會變化的。

最后再來介紹兩個宏定義,這個是針對於類型對象的,我們后面在介紹類型對象的時候會經常見到這兩個宏定義。

// Include/object.h
#define PyObject_HEAD_INIT(type)        \
    { _PyObject_EXTRA_INIT              \
    1, type },


#define PyVarObject_HEAD_INIT(type, size)       \
    { PyObject_HEAD_INIT(type) size },

先看PyObject_HEAD_INIT,里面的_PyObject_EXTRA_INIT是用來實現refchain這個雙向鏈表的,我們目前不需要管。里面的1指的是引用計數,我們看到剛創建的時候默認是設置為1的,至於type就是該類型對象的類型了,這個是作為宏的參數傳進來的;而PyVarObject_HEAD_INIT,則是在PyObject_HEAD_INIT的基礎之上,增加了一個size,顯然我們從名字也能看出來這個size是什么。當然目前只是介紹這兩個宏,先有個印象,類型對象的實現我們下面就會說。

實現類型對象的基石--PyTypeObject

通過PyObject和PyVarObject,我們看到了Python中所有對象的共有信息以及變長對象的共有信息。對於任何一個對象,不管它是什么類型,內部必有引用計數(ob_refcnt)和類型指針(ob_type);對於任意一個變長對象,不管它是什么類型,除了引用計數和類型指針之外,內部還有一個表示元素個數的ob_size。

顯然目前是沒有什么問題,一切都是符合我們的預期的,但是當我們順着時間軸回溯的話,就會發現端倪。比如:

  • 1. 當在內存中創建對象、分配空間的時候,解釋器要給該對象分配多大的空間?顯然不能隨便分配,那么該對象的內存信息在什么地方?
  • 2. 一個對象是支持相應的操作的,解釋器怎么判斷該對象支持哪些操作呢?再比如一個整型可以和一個整型相乘,但是一個列表也可以和一個整型相乘,即使是相同的操作,但不同類型的對象執行也會有不同的結果,那么此時解釋器又是如何進行區分的?

想都不用想,這些信息肯定都在對象所對應的類型對象中。而且占用的空間大小實際上是對象的一個元信息,這樣的元信息和其所屬類型是密切相關的,因此它一定會出現在與之對應的類型對象當中。至於支持的操作就更不用說了,我們平時自定義類的時候,方法都寫在什么地方,顯然都是寫在類里面,因此一個對象支持的操作顯然定義在類型對象當中。

而將一個對象和其類型對象關聯起來的,毫無疑問正是該對象內部的PyObject中的ob_type,也就是類型指針。我們通過對象的ob_type成員即可獲取指向的類型對象的指針,通過該指針可以獲取存儲在類型對象中的某些元信息。

下面我們來看看類型對象在底層是怎么定義的:

//Include/object.h
typedef struct _object {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject; //_typeobject正是PyObject里面的一個成員

// 類型對象對應的結構體
typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name;
    Py_ssize_t tp_basicsize, tp_itemsize; 
    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    reprfunc tp_repr;
    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;
    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;
    PyBufferProcs *tp_as_buffer;
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */


    traverseproc tp_traverse;

    inquiry tp_clear;
    richcmpfunc tp_richcompare;

    Py_ssize_t tp_weaklistoffset;

    getiterfunc tp_iter;
    iternextfunc tp_iternext;
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;
    unsigned int tp_version_tag;

    destructor tp_finalize;

#ifdef COUNT_ALLOCS
    Py_ssize_t tp_allocs;
    Py_ssize_t tp_frees;
    Py_ssize_t tp_maxalloc;
    struct _typeobject *tp_prev;
    struct _typeobject *tp_next;
#endif
} PyTypeObject;
#endif

類型對象在底層對應的是struct _typeobject,當然也是PyTypeObject,它里面的成員非常非常多,我們暫時挑幾個重要的說,因為有一部分成員並不是那么重要,我們在后續會慢慢說。

目前我們了解到Python中的類型對象在底層就是一個PyTypeObject實例,它保存了實例對象的元信息,描述對象的類型。

Python中的實例對象在底層對應不同的結構體實例,而類型對象則是對應同一個結構體實例,換句話說無論是int、str、dict等等等等,它們在C的層面都是由PyTypeObject這個結構體實例化得到的,只不過成員的值不同PyTypeObject這個結構體在實例化之后得到的類型對象也不同。

我們看一下PyTypeObject內部幾個非常關鍵的成員:

  • PyObject_VAR_HEAD:我們說這是一個宏,對應一個PyVarObject,所以類型對象是一個變長對象。而且類型對象也有引用計數和類型,這與我們前面分析的是一致的。
  • tp_name:類型的名稱,而這是一個char *,顯然它可以是int、str、dict之類的。
  • tp_basicsize, tp_itemsize:創建對應實例對象時所需要的內存信息。
  • tp_dealloc:其實例對象執行析構函數時所作的操作。
  • tp_print:其實例對象被打印時所作的操作。
  • tp_as_number:其實例對象為數值時,所支持的操作。這是一個結構體指針,指向的結構體中的每一個成員都是一個函數指針,其函數就是整型對象可以執行的操作,比如:四則運算、左移、右移、取模等等
  • tp_as_sequence:其實例對象為序列時,所支持的操作。同樣是一個結構體指針。
  • tp_as_mapping:其實例對象為映射時,所支持的操作。也是一個結構體指針。
  • tp_base:繼承的基類。

我們暫時就挑這么幾個,事實上從名字上你也能看出來這每一個成員代表的含義。而且這里面的成員雖然多,但並非每一個類型對象都具備,比如int類型它就沒有tp_as_sequence和tp_as_mapping,所以int類型的這兩個成員的值都是0。

具體的我們就在分析具體的類型對象的時候再說吧,然后先來看看Python對象在底層都叫什么名字吧。

  • 整型 -> PyLongObject結構體實例, int -> PyLong_Type(PyTypeObject結構體實例)
  • 字符串 -> PyUnicodeObject結構體實例, str -> PyUnicode_Type(PyTypeObject結構體實例)
  • 浮點數 -> PyFloatObject結構體實例, float -> PyFloat_Type(PyTypeObject結構體實例)
  • 復數 -> PyComplexObject結構體實例, complex -> PyComplex_Type(PyTypeObject結構體實例)
  • 元組 -> PyTupleObject結構體實例, tuple -> PyTuple_Type(PyTypeObject結構體實例)
  • 列表 -> PyListObject結構體實例, list -> PyList_Type(PyTypeObject結構體實例)
  • 字典 -> PyDictObject結構體實例, dict -> PyDict_Type(PyTypeObject結構體實例)
  • 集合 -> PySetObject結構體實例, set -> PySet_Type(PyTypeObject結構體實例)
  • 不可變集合 -> PyFrozenSetObject結構體實例, frozenset -> PyFrozenSet_Type(PyTypeObject結構體實例)
  • 元類:PyType_Type(PyTypeObject結構體實例)

所以Python中的對象在底層的名字都遵循一定的標准,包括解釋器提供的Python/C API也是如此。

下面以浮點數為例,考察一下類型對象和實例對象之間的關系。

浮點類型我們說底層對應的是PyTypeObject的實例PyFloat_Type,並且浮點類型是全局唯一的;而浮點數則是PyFloatObject實例,浮點數可以有任意個,比如:圓周率pi是一個、自然對數e又是一個。

>>> float
<class 'float'>
>>> pi = 3.14
>>> e = 2.71
>>>
>>> type(pi) is type(e) is float
True
>>>

兩個變量均指向了浮點數(PyFloatObject結構體實例),除了公共頭部字段ob_refcnt和ob_type,專有字段ob_fval保存了對應的數值;浮點類型float則對應PyTypeObject結構體實例(PyFloat_Type),保存了類型名、內存分配信息以及浮點數相關操作。而將這兩者關聯起來的就是ob_type這個類型指針,它位於PyObject中,是所有對象共有的,而Python便是根據這個ob_type來判斷該對象的類型,進而獲取該對象的元信息。

我們說變量只是一個指針,那么int、float、dict這些是不是變量,顯然是的,函數和類也是一個變量,所以它們在底層也是一個指針。只不過這些變量是內置的,直接指向了具體的PyTypeObject實例。只是為了方便,有時我們用int、float等等,來代指指向的對象。比如:float指向了底層的PyFloat_Type,所以它其實是PyFloat_Type的指針,但為了表述方便我們會直接用float來代指PyFloat_Type。

而且類型對象在解釋器啟動的時候就已經是創建好了的,不然的話我們怎么能夠直接用呢?類型對象創建完畢之后,直接讓float指向相應的類型對象。

我們來看一下float對應的類型對象在底層是怎么定義的吧。

// Object/floatobject.c
PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),
    0,
    (destructor)float_dealloc,                  /* tp_dealloc */

    // ...
    (reprfunc)float_repr,                       /* tp_repr */

    // ...
};

我們看到PyFloat_Type在源碼中就直接被創建了,這是必須的,否則我們就沒有辦法直接訪問float這個變量了,然后先看結構體中的第4行,我們看到tp_name被初始化成了"float";第5行表示實例對象所占的字節數,我們看到就是一個PyFloatObject實例所占的內存大小,並且顯然這個值是不會變的,說明無論創建多少個實例對象,它們的大小都是不變的,這也符合我們之前的測試結果,都是24字節。

再往下就是一些各種操作對應的函數指針,最后我們來看一下第3行,顯然它接收的是一個PyVarObject,PyVarObject_HEAD_INIT這個宏無需贅言,但重點是里面的&PyType_Type,說明了float被設置成了type類型。

>>> float.__class__
<class 'type'>
>>> # 顯然這是符合我們的預期的

而且所有的類型對象(還有元類)在底層都被定義成了靜態的全局變量,因為它們的聲明周期是伴隨着整個解釋器的,並且在任意地方都可以訪問。

類型對象的類型--PyType_Type

我們考察了float類型對象,知道它在C的層面是PyFloat_Type這個靜態全局變量,它的類型是type,包括我們自定義的類的類型也是type。而type在Python中是一個至關重要的對象,它是所有類型對象的類型,我們稱之為元類型(meta class),或者元類。借助元類型,我們可以實現很多神奇的高級操作。那么type在C的層面又長啥樣呢?

在介紹PyFloat_Type的時候我們知道了type在底層對應PyType_Type,而它在"Object/typeobject.c"中定義,因為我們說所有的類型對象加上元類都是要預先定義好的,所以要源碼中就必須要以靜態全局變量的形式出現。

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */
    (destructor)type_dealloc,                   /* tp_dealloc */

    // ...
    (reprfunc)type_repr,                        /* tp_repr */

    // ...
};

我們所有的類型對象加上元類都是PyTypeObject這個結構體實例化得到的,所以它們內部的成員都是一樣的,只不過傳入的值不同,實例化之后的結果也不同,可以是PyLong_Type、可以是PyFloat_Type,也可以是這里的PyType_Type。

PyType_Type的內部成員和PyFloat_Type是一樣的,但是我們還是要重點看一下里面的宏PyVarObject_HEAD_INIT,我們看到它傳遞的是一個&PyType_Type,說明它把自身的類型也設置成了PyType_Type,換句話說,PyType_Type里面的ob_type成員指向的還是PyType_Type。

>>> type.__class__
<class 'type'>
>>> type.__class__.__class__.__class__.__class__.__class__ is type
True
>>> type(type(type(type(type(type))))) is type
True
>>>

顯然不管我們套娃多少次,最終的結果都是True,顯然這也是符合我們的預期的。

類型對象的基類--PyBaseObject_Type

我們說Python中有兩個類型對象比較特殊,一個是站在類型金字塔頂端的type,一個是站在繼承金字塔頂端的object。說完了type,我們來說說object,我們說類型對象內部的tp_base表示繼承的基類,對於PyType_Type來講,它內部的tp_base肯定是PyBaseObject_Type。

但令我們吃鯨的是,它的tp_base居然是個0,如果為0的話則表示沒有這個屬性。

0,                                          /* tp_base */

不是說type的基類是object嗎?為啥tp_base是0,事實上如果你去看PyFloat_Type的話,它內部的tp_base也是0。為0的原因就在於我們目前看到的類型對象是一個半成品,因為Python的動態性,顯然不可能在定義的時候就將所有成員屬性都設置好、然后解釋器一啟動就會得到我們平時使用的類型對象。目前看到的類型對象是一個半成品,有一部分成員屬性是在解釋器啟動之后再進行動態完善的。

至於是怎么完善的,都有哪些成員需要解釋器啟動之后才能完善,我們后續系列會說。

而PyBaseObject_Type位於Object/object.c中,我們來一睹其芳容。

PyTypeObject PyBaseObject_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "object",                                   /* tp_name */
    sizeof(PyObject),                           /* tp_basicsize */
    0,                                          /* tp_itemsize */
    object_dealloc,                             /* tp_dealloc */

    // ...
    object_repr,                                /* tp_repr */
    // ...
};

我們看到PyBaseObject_Type的類型也被設置成了PyType_Type,而PyType_Type類型在被完善之后,它的tp_base也會指向PyBaseObject_Type。所以之前我們說Python中的type和object是同時出現的,它們的定義是需要依賴彼此的。

>>> object.__class__
<class 'type'>
>>>

注意:解釋器在完善PyBaseObject_Type的時候,是不會設置其tp_base的,因為繼承鏈必須有一個終點,否對象沿着繼承鏈進行屬性查找的時候就會陷入死循環,而object已經是繼承鏈的頂點了。

>>> print(object.__base__)
None
>>>
  • object -> PyBaseObject_Type
  • object() -> PyBaseObject

小結

至此,我們算是從解釋器的角度完全理清了Python中對象體系,其實我們之前畫的圖已經將Python對象體系表達的很清晰了,如下:

我們之前花了很大一部分筆墨來從Python的角度介紹其對象體系,之所以這么做就是為了能夠更好地理解本篇內容。如果能在Python層面上充分理解的話,那么在CPython層面上理解也就不難了。

而且我們還介紹了PyObject、PyVarObject,並分析了Python中的type和object在底層的實現,雖然還肯定遠遠不夠,但對於當前來說已經邁出一大步了。我們在后續系列中會針對Python中類型對象進行單獨剖析,到時候再來挖掘更加細致的內容。


免責聲明!

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



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