欢迎关注微信公众号——Python与统计分析,一起学习,一起交流。


相信所有学过Python的人都听过这样一句话:Python中,一切皆对象。一个整数是一个对象,产生这个整数的类int也是一个对象。函数是一个对象,函数所在的模块也是一个对象。最让我们惊讶的是,就连代码本身也是对象。

Python把面向对象的思想发挥的淋漓尽致,然而对于初学者来说,我们还是不清楚什么是对象。Python官方解释器是由C语言编写的,解答这个问题需要从C语言中寻找答案。因此本文,我们主要从C的层面来认识对象。

对象机制的核心——PyObject

对于人来说,对象是一个比较具体的概念,比如说一个人,一张书桌。而对于计算机而言,对象却是一个抽象的概念。通常的说法是,对象是数据以及操作这些数据的方法的集合。由于数据和操作数据的方法都存储在计算机内存中,你可以理解一个对象就是计算机中存储这些数据和方法的一块内存空间,而这块内存可能是连续的,也可能是不连续的。如果是不连续的,我们需要从整体上来把握。再具体一些,从C的层面来看,Python的对象就是为C中的结构体在内存堆空间中申请的一块内存。

Python中有很多种类型的对象,不同类型的对象之间存在一些共性,这些共性定义在PyObject结构体中。

从Python官网下载Python解释器的源码包,解压打开Includeobject.h,我们就可以看到PyObject了,PyObject的定义如下:

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

可以看到PyObject内部定义了一个_PyObject_HEAD_EXTRA、一个ob_refcnt和一个ob_type_PyObject_HEAD_EXTRA是一个宏,这个宏主要使用来维护一个“双向链表”的,通常不启用,可以不用管。我们的关注点放在ob_refcntob_type

  • ob_refcnt:即reference count,即引用计数。
  • ob_type:类型指针。

引用计数表示该对象被变量引用的次数,对象被引用1次,ob_refcnt就会加1,引用解除时,ob_refcnt就会减少1。引用计数是Python实现对象回收的重要机制之一。

我们注意到ob_type指向了一个_typeobject的结构体,这个结构体是做什么的呢?我们说对象有三个重要特征:id、类型和值。_typeobject就是用来指定一个对象的类型的。这部分内容我们后面再进行分析。

现在我们看到了,Python中对象至少包含两部分内容,一个是引用计数,一个是类型,这些是对象的共性,然而还有一些性质虽然不是所有对象所共有,但是是大部分对象所共有的,接下来我们就会看到。

定长对象与变长对象

Python中根据对象所占用的内存空间大小是否固定,可以将对象分为定长对象和变长对象。定长对象如整数。变长对象如字符串、列表等。字符串、列表中有多少个元素,都无法事先确定,只能使用变长对象来进行存储,变长对象在C层面对应于PyVarObject结构体。

同样可以在object.h中找到PyVarObject结构体:

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

可以看到,PyVarObject是对PyObject的扩展,在其基础上增加一个ob_size的字段。ob_size指明了变长对象中容纳了多少个元素。比如对于一个list,它就是一个变长对象,list中有5个元素,ob_size就等于5。

Python中的对象都是在PyVarObject或者PyObject基础上进行的扩展,例如:float实例对象,其结构体定义为:

typedef struct {PyObject_HEADdouble ob_fval;
} PyFloatObject;

可见,float实例对象在内存中对应PyFloatObject结构体,其是在PyObject基础上增加了一个双精度的ob_fval,其代表的就是该实例对象的值。

再如list实例对象,其结构体的定义为:

typedef struct {PyObject_VAR_HEADPyObject **ob_item;Py_ssize_t allocated;
} PyListObject;

list实例对象是一个PyListObject的结构体,是在PyVarObject的基础之上增加了两个字段:

  • ob_item: 指向 动态数组 的指针,数组保存元素对象指针;
  • allocated: 动态数组总长度,即列表当前的 容量

float实例对象和list实例对象在介绍相应数据类型的时候再进行阐述。

类对象

除了实例化对象之外,Python中类也是一种对象,我们称之为类对象。类对象之间也有一些共性,接下来我们看就来看一下。

首先我们要明确,类对象作为一种对象,其基本结构应该是在PyObject基础上扩展的,具体而言Python中的类对象是以PyTypeObject结构体为基础的,现在我们请出PyTypeObject的结构体,看看其真身如何。

typedef struct _typeobject {PyObject_VAR_HEADconst 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_ALLOCSPy_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

PyTypeObject中的字段非常多,我们首先看到的是PyObject_VAR_HEAD,这是一个宏,其实就是提示PyTypeObject是一个变长对象。其他一些关键字段包括:

  • tp_name:类对象的名称,是一个char 。
  • tp_basicsize, tp_itemsize:创建对应实例对象时所需要的内存信息。
  • tp_dealloc:其实例对象执行析构函数时所作的操作。
  • tp_print:其实例对象被打印时所作的操作。
  • tp_base:继承的基类。

PyTypeObject结构体初始化设置不同的值,就会得到Python中不同的内置类对象,例如float类对象的结构体如下:

PyTypeObject PyFloat_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)"float",sizeof(PyFloatObject),0,(destructor)float_dealloc,                  /* tp_dealloc */0,                                          /* tp_vectorcall_offset */0,                                          /* tp_getattr */0,                                          /* tp_setattr */0,                                          /* tp_as_async */(reprfunc)float_repr,                       /* tp_repr */&float_as_number,                           /* tp_as_number */0,                                          /* tp_as_sequence */0,                                          /* tp_as_mapping */(hashfunc)float_hash,                       /* tp_hash */0,                                          /* tp_call */0,                                          /* tp_str */PyObject_GenericGetAttr,                    /* tp_getattro */0,                                          /* tp_setattro */0,                                          /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */float_new__doc__,                           /* tp_doc */0,                                          /* tp_traverse */0,                                          /* tp_clear */float_richcompare,                          /* tp_richcompare */0,                                          /* tp_weaklistoffset */0,                                          /* tp_iter */0,                                          /* tp_iternext */float_methods,                              /* tp_methods */0,                                          /* tp_members */float_getset,                               /* tp_getset */0,                                          /* tp_base */0,                                          /* tp_dict */0,                                          /* tp_descr_get */0,                                          /* tp_descr_set */0,                                          /* tp_dictoffset */0,                                          /* tp_init */0,                                          /* tp_alloc */float_new,                                  /* tp_new */
};

float类对象是一个PyFloat_Type的结构体。第二行是初始化PyTypeObject的 ob_refcntob_type 以及 ob_size 三个字段 ,第三行是初始化PyTypeObject的tp_name字段为float,......,这样Python内置的类对象float就诞生了。

现在我们应该清楚了,一个PyTypeObject对象就是面向对象理论中类对象的实现,我们熟知的int类、float类、list类都是一个PyTypeObject结构体,都是在这个结构体基础上进行了一些初始化。从PyTypeObject结构体中我们可以推测,其中存储了很多实例化对象的信息,这些信息在后面我们再详细介绍。

有了上面的基础,下面我们再来看一下Python中两个特殊的对象:type对象和object对象。

Type对象

type到底是一个什么对象呢?我们说类也是一个对象,既然类是一个对象,在计算机底层必然至少存在一个引用计数,一个类型指针,而这个类型指针指向的就是这个类对象的类型对象。类的类型对象是什么呢?先别急,我们来作一个实验:

In [1]: float.__class__
Out[1]: typeIn [2]: int.__class__
Out[2]: typeIn [3]: list.__class__
Out[3]: type

Python中打印一个对象的__class__属性就会返回这个对象的类型对象。我们看到,float类对象、int类对象、list类对象的__class__属性返回值都是type,也就是说float类对象、int类对象、list类对象的类型对象都是type对象。实际上,Python中所有内置类对象的类型对象都是type对象。那么type对象在计算机中长什么样子呢?

// Objects/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 */offsetof(PyTypeObject, tp_vectorcall),      /* tp_vectorcall_offset */0,                                          /* tp_getattr */0,                                          /* tp_setattr */0,                                          /* tp_as_async */(reprfunc)type_repr,                        /* tp_repr */0,                                          /* tp_as_number */0,                                          /* tp_as_sequence */0,                                          /* tp_as_mapping */0,                                          /* tp_hash */(ternaryfunc)type_call,                     /* tp_call */0,                                          /* tp_str */(getattrofunc)type_getattro,                /* tp_getattro */(setattrofunc)type_setattro,                /* tp_setattro */0,                                          /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |Py_TPFLAGS_HAVE_VECTORCALL,                 /* tp_flags */type_doc,                                   /* tp_doc */(traverseproc)type_traverse,                /* tp_traverse */(inquiry)type_clear,                        /* tp_clear */0,                                          /* tp_richcompare */offsetof(PyTypeObject, tp_weaklist),        /* tp_weaklistoffset */0,                                          /* tp_iter */0,                                          /* tp_iternext */type_methods,                               /* tp_methods */type_members,                               /* tp_members */type_getsets,                               /* tp_getset */0,                                          /* tp_base */0,                                          /* tp_dict */0,                                          /* tp_descr_get */0,                                          /* tp_descr_set */offsetof(PyTypeObject, tp_dict),            /* tp_dictoffset */type_init,                                  /* tp_init */0,                                          /* tp_alloc */type_new,                                   /* tp_new */PyObject_GC_Del,                            /* tp_free */(inquiry)type_is_gc,                        /* tp_is_gc */
};

很显然,type对象与类对象float、int等一样,也是一种PyTypeObject的结构体实例,这个结构体实例名称为PyType_Type,只不过在初始化赋值时,接收的是和float、int不同的参数。比如说在第三行,PyTypeObject的tp_name字段接收一个"type"的值,这表明这个PyTypeObject结构体实例是一个被称为type的对象。第十二行,tp_as_number字段接收一个0的值,这表明type对象并未定义数学运算相关方法。其他字段在我们后续的推送中会逐一进行讲解,现在我们需要考虑一个问题,type是一个PyTypeObject结构体实例,是一个对象,那么它的类型对象是什么?

这个秘密隐藏在PyType_Type结构体实例的第二行中。我们注意到第二行PyVarObject_HEAD_INIT是一个带参数的宏,这个宏在CPython中的定义如下:

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

宏PyVarObject_HEAD_INIT中包含了另外一个宏PyObject_HEAD_INIT。而后者接收一个type的参数,用来初始化PyObject。具体而言,当创建一个PyObject时,调用这个宏,可以将对象的引用计数ob_refcnt字段初始化为1,将ob_type初始化为type。以此类推,PyVarObject_HEAD_INIT宏是用来初始化一个PyVarObject的,其增加了一个参数,用来指定PyVarObject的长度,即初始化ob_size字段。

有了上面的介绍,我们再来看一下PyType_Type结构体实例的第二行。我们看到,PyType_Type的宏PyVarObject_HEAD_INIT接收的两个参数分别为&PyType_Type和0,这表明Python解释器将type对象的ob_type字段初始化为PyType_Type,而将ob_size字段出视化为0。

什么?type对象的ob_type字段初始化为PyType_Type?这不是意味着type对象的类型对象是自己吗?确实如此,且看如下演示:

In [4]: type.__class__
Out[4]: type

现在我们应该对type对象有了一定认识了,Python中内置类对象的类型对象都是type对象,而type对象的类型对象是本身。由于type对象是类对象的类型对象,通常把type对象称之为元类(metaclass)。

Object对象

说完了type元类对象,我们再来说说object对象。我们在学习Python编程时,我们说用class语句自定义一个类,如:

In [5]: class A:...:     pass...:

那么,类A默认会继承自一个object类。而且对于一个类,我们打印其__base__属性,就会返回这个类所继承的父类:

In [6]: A.__base__
Out[6]: object

结果符合我们的预期。那么object又是什么样子呢?

// Objects/typeobject.c
PyTypeObject PyBaseObject_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)"object",                                   /* tp_name */sizeof(PyObject),                           /* tp_basicsize */0,                                          /* tp_itemsize */object_dealloc,                             /* tp_dealloc */0,                                          /* tp_vectorcall_offset */0,                                          /* tp_getattr */0,                                          /* tp_setattr */0,                                          /* tp_as_async */object_repr,                                /* tp_repr */0,                                          /* tp_as_number */0,                                          /* tp_as_sequence */0,                                          /* tp_as_mapping */(hashfunc)_Py_HashPointer,                  /* tp_hash */0,                                          /* tp_call */object_str,                                 /* tp_str */PyObject_GenericGetAttr,                    /* tp_getattro */PyObject_GenericSetAttr,                    /* tp_setattro */0,                                          /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */object_doc,                                 /* tp_doc */0,                                          /* tp_traverse */0,                                          /* tp_clear */object_richcompare,                         /* tp_richcompare */0,                                          /* tp_weaklistoffset */0,                                          /* tp_iter */0,                                          /* tp_iternext */object_methods,                             /* tp_methods */0,                                          /* tp_members */object_getsets,                             /* tp_getset */0,                                          /* tp_base */0,                                          /* tp_dict */0,                                          /* tp_descr_get */0,                                          /* tp_descr_set */0,                                          /* tp_dictoffset */object_init,                                /* tp_init */PyType_GenericAlloc,                        /* tp_alloc */object_new,                                 /* tp_new */PyObject_Del,                               /* tp_free */
};

object显然也是一个以PyTypeObject结构体为基础的类对象,这个结构体名字为PyBaseObject_Type。当PyTypeObject结构体在初始化为PyBaseObject_Type时,我们注意到tp_name字段被初始化为'object',ob_type字段被初始化为PyType_Type。因此,我们在解释器中输入如下命令,返回object对象的类型对象为type对象。

In [7]: object.__class__
Out[7]: type

我们再来介绍一个字段tp_base,PyTypeObject结构体初始化时,tp_base字段用来设置对象所继承的父类,我们发现这里的tp_base字段初始化设置为0,这表明object的父类为空。Python解释器提供了__base__属性可以查看一个类对象所继承的父类:

In [8]: print(object.__base__)
None

结果是符合预期的。那么为什么要将object对象的父类设置为空呢?因为继承链必须有一个终点,object被Python解释器设定为“对象之父”,也就是说一切对象都继承自object,那么继承链到达object这里必须要停止,否则就会陷入无穷的循环之中。

学习了一个新的字段tp_base,相信你一定会回头看看之前介绍的type元类对象和float类对象对应结构体实例的tp_base 字段设置吧。我们检查发现,这两个字段都被设置为了0。怎么回事?这两个字段不应都被设置为object吗?

In [11]: type.__base__
Out[11]: object
​
In [12]: float.__base__
Out[12]: object

实际上,你会发现所有内置类对象的tp_base字段都被初始化为0。为0的原因在于目前我们看到的类对象都是一个静态的半成品。Python是一个动态语言,不可能在定义的时候将所有字段都设置好,然后在解释器启动时就得到我们使用的类型对象。实际上这些类对象对应结构体在解释器启动后,还会再进行一次动态的完善,届时就会得到我们最终使用的类对象。

至于怎么完善,以及那些字段会完善,我们还是放在后面再进行讲解。

总结

至此,我们比较系统的介绍了Python中的主要对象及其在计算机中的表现形式。接下来我们通过一个例子,以图示的形式来完整介绍一下实例对象、类对象、type元类对象、object类对象之间的关系。如图所示:

In [1]: e = 2.71828
​
In [2]: e.__class__
Out[2]: float
​
In [3]: float.__class__
Out[3]: type
​
In [4]: float.__base__
Out[4]: object
​
In [5]: type.__class__
Out[5]: typeIn [6]: type.__base__
Out[6]: object
​
In [7]: object.__class__
Out[7]: type
​
In [8]: object.__base__
​
In [9]: e.__base__
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<iPython-input-9-e38e26eb69ec> in <module>
----> 1 e.__base__
​
AttributeError: 'float' object has no attribute '__base__'

  • 在Python解释器中输入的e、float、type、object都仅仅是一个名字,这些名字分别指向内存中的四个结构体,分别为PyFloatObject、PyFloatType、PyType_Type、PyBaseObject_Type
  • 这些结构体都有的部分是PyObject,包含一个引用计数,一个类型指针。
  • 所有对象都有类型对象,e实例的类型指针指向了float类对象,表明e是一个float类型。同样float类对象和object类对象的类型指针都指向了type元类对象,表明float类对象和object类对象的类型对象为type。此外,type元类对象的类型对象是自身。
  • 类对象包括一个ob_base字段,表明类对象都有基类。float类对象和type类对象的基类都是object类,而object类的基类为空。

上图较详细的呈现了Python中对象之间的关系,我们将这些关系称之为对象模型。对象模型将是理解Python运行机制的核心,大家应该牢牢掌握。

参考资料

[1] 陈儒.Python源码剖析——深度探索动态语言核心技术[M].电子工业出版社:北京,2008:1-497
[2] fasionchan.Python 源码深度剖析[EB/OL].https://www.imooc.com/read/76,2020.
[3] Python.Python对象实现支持[EB/OL].https://docs.python.org/zh-cn/3/c-api/objimpl.html
[4] Obi lke-Nwosu.Inside the Python Virtual Machine[M].Leanpub, 2018:1-126

python结构体_Python对象初探相关推荐

  1. python结构体_Python实现结构体代码实例

    Python实现结构体代码实例 这篇文章主要介绍了Python实现结构体代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 # python 使 ...

  2. ctypes python 结构体_Python 中 ctypes 的使用

    Python 的 ctypes 要使用 C 函数,需要先将 C 编译成动态链接库的形式,即 Windows 下的 .dll 文件,或者 Linux 下的 .so 文件.先来看一下 ctypes 怎么使 ...

  3. C++ VS C# 结构体和对象的细微区别

    C++ VS C# 结构体和对象的细微区别 类与结构体在C++与C#中都是可以使用的,但是两者在两种语言中的区别却很大,今天在这里进行一下记录. 在C++中,类与结构体的唯一区别就是默认访问权限的区别 ...

  4. 结构体内容引用自非结构体数组对象axes(handles.axes1)

    Matlab结构体内容引用自非结构体数组对象 matlab的gui报错axes(handles.axes1) 如何解决 起因 代码 matlab的gui报错axes(handles.axes1) 废话 ...

  5. STL 的 std::set 创建自定义结构体的对象,定义严格弱序的比较函数

    文章目录 正文 Ref 系列地址 简 述: 对于 STL 中 std::set 创建自定义结构体的对象,发现使用 "函数对象" 和 "定义普通函数 + decltype& ...

  6. python 结构体嵌套_Python 3不更新嵌套包中的变量(使用“递归”相对导入)

    我正在重构一个大型程序程序(在一个文件夹中实现了许多文件),并使用包将文件分组为面向对象的结构.该应用程序使用tKinter(可能是红色鲱鱼),并且正在Eclipse Kepler上(在Win7上)使 ...

  7. python 结构体数组_python实现结构体数组(初始化并赋值)

    标签: C语言中结构体数组概念及定义 一个结构体变量可以存放一个学生的一组信息,可是如果有 10 个学生呢?难道要定义 10 个结构体变量吗?难道上面的程序要复制和粘贴 10 次吗? 很明显不可能,这 ...

  8. python 结构体数组 定义_一篇文章弄懂Python中所有数组数据类型

    前言 数组类型是各种编程语言中基本的数组结构了,本文来盘点下Python中各种"数组"类型的实现. list tuple array.array str bytes bytearr ...

  9. JNI调用c++函数,该函数的参数是结构体(——对象的传递)

    第三方C++函数接口为 int api_get_logfile(Struct fileinfo tfile),参数是个结构体,且套了另一个结构体: struct fileinfo{  char *fu ...

最新文章

  1. CoAP 协议解析说明(转)
  2. Hadoop书籍和网络资源介绍
  3. Docker 练习(一)——搭建web服务
  4. JavaScript中带有示例的Math.log()方法
  5. Android 系统(56)---深入浅析Android坐标系统
  6. python进阶12并发之八多线程与数据同步
  7. vs2005中分割线怎么插入
  8. SpringBoot❤SpringClould常用注解史诗级汇总
  9. 【C语言工具】AddressSanitizer - 内存检测工具
  10. QT安装后编译错误怎么办?
  11. BIGEMAP下载离线地图数据(支持谷歌、百度、高德等所有地图源)
  12. python2.0安装教程_Python2.7 【安装教程】
  13. librdkafka自动源码编译
  14. sphinxapi.php 详解,【转】Sphinx PHP api全文检索的例子
  15. 笔记本安装双系统教程
  16. VirtualBox+CentOS6.5安装增强功能包 - Building the main Guest Additions module [失败]
  17. 中国大学MOOC浙江大学“程序设计入门——C语言”的PTA练习题目集答案
  18. 我的世界java版高效率刷怪塔_我的世界超高效率刷怪塔制作教程 砍怪砍到手抽筋...
  19. Python虚拟环境之pyenv
  20. PCIE协议解析 synopsys IP Power Management Capability 读书笔记(10)

热门文章

  1. 计算机快捷键下档健,电脑文档快捷键
  2. qt android刘海屏状态栏,华为Mate30 Pro设计曝光:仍配刘海屏+3D结构光
  3. android返回页面代码实现,Intent携带信息返回上一个界面实现基本信息的设置
  4. Linux窗口按钮大小,Fitts: 给Ubuntu窗口一个大按钮
  5. java替换带特殊字符的字符串6_Java字符串替换特殊字符(保加利亚语,波兰语,德语)...
  6. java script数据类型_typescript 基本数据类型
  7. zabbix前端php界面,Zabbix前端插件zatree在Zabbix 2.2.1前端报错的解决方案
  8. python写的小程序怎么封装_微信小程序源码分享之封装request的方法
  9. 如何安装html网站模板,网站模板安装说明
  10. oracle mysql 数据类型对比_Oracle、SQL Server、MySQL数据类型对比