python底层设计:对象设计思想

目录

1.对象机制的基石PyObject

2.可变对象类型基石:PyVarObject

3.类的设计核心PyTypeObject

3.1.对象的创建原理

3.2 对象行为

3.3 类型的类型

4.对象的多态/动态语言的原理

5.计数引用


在python中,很多模块都是直接由c实现的,所有东西都是对象,即使是int,float等这样的基本数据类型,都是一个对象,比如

>>> import sys
>>> print(sys.getsizeof(int))

416
>>> 

一个int就占用416比特,正是因为python对所有东西都封装成了对象,所以python程序运行需要占用的内存就比c,c++,java等要多得多。

在接下来的说明中,一切都参照python3.7.5源码作讲解。

 

1.对象机制的基石PyObject

 

在python中,所有东西都是对象,所有对象拥有相同的部分内容,而这内容就是PyObject,所以对象的核心是PyObject。

我们先看看PyObject的定义:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

ob_refcnt其实是一个计数器,与内存管理机制有关,对于对象A,当 新PyObject*引用A对象时,A的计数器+1,当删除这个PyObject*时A的计数器-1,当A计数器为0时,A将从堆中被回收。

ob_type是指明一个对象类型的类型对象,_typeobject结构体是python中内部特殊的对象。

所以在python中,对象机制的核心非常简单:引用计数器+类型信息

PyObject是所有类型对象都必须拥有的,它必须放在对象内存占用最开始的那个字节,除了PyObject,还可以新增额外的信息,构成其他类型的对象,比如float类型的结构设计是:

typedef struct {
    PyObject_HEAD //PyObject_HEAD定义每个PyObject的初始段。
    double ob_fval;
} PyFloatObject;

我们可以看出,float除了PyObject,还有而外的double变量,ob_fval用来存放数值。同理,list,map,set,str等所有对象也是如此:在PyObject之外还定义属于自身的特殊信息。

 

 

2.可变对象类型基石PyVarObject

 

定长对象:拥有固定大小,如int,float等

变长对象:可变元素个数,如str,list等

我们知道,在c 中没有诸如java,python,c++的字符串对象,准确来说,字符串对象维护的是多个char变量,换句话说,python中字符串对象维护的是n个PyObject对象,类似list,dict等对象拥有共同的特征:“n个PyObject”,于是乎就有了PyObject的扩展PyVarObject,它的结构体设计:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* 可变部分的对象数 */
} PyVarObject;

ob_size记录的是变长对象中容纳了几个元素,比如list有5个元素,那么ob_size就等于5.

每个对象头部都有统一的PyObject,这使得对对象的引用变得非常统一,我们只需一个PyObject* 指针就可以指向任意一个对象。

python底层设计:对象设计思想_第1张图片

 

 

3.类的设计核心PyTypeObject

一个类固然会包含大小,函数等信息,那它在python是怎么设计的呢?下面是类的设计源码

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "." */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    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;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    Py_ssize_t tp_weaklistoffset;

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    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;

    /* Type attribute cache version tag. Added in version 2.6 */
    unsigned int tp_version_tag;

    destructor tp_finalize;

#ifdef COUNT_ALLOCS
    /* these must be last and never explicitly initialized */
    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;

上面的源码我们可以看出:类定义包括了

  • 类名tp_name;
  • 创建对象时分配的内存空间信息tp_basicsize,tp_itemsize;
  • 定义对象行为的函数指针如析构指针,操作族指针等

 

3.1.对象的创建原理

>>> __builtins__.__dict__['int']

>>> int.__base__

>>> 

从这段代码我们可以看出,int类型的基类是object,int对应python内部的PyInt_Type,object对应PyBaseObject_Type,下面使用c++中int类型的创建在python内部的创建过程

python底层设计:对象设计思想_第2张图片

int(10)创建过程:首先调用PyInt_Type中的tp_new,如果tp_new为null(实际不可能为null的),那么会到tp_base对应的基类PyBaseObject_Type调用的tp_new,由于该tp_new指向object中的object_new,在object_new中,会访问PyInt_Type中的tp_basicsize信息(确定类需要占用内存的大小),继而完成申请内存操作,完成(创建对象)之后转向调用PyInt_Type的tp_init完成初始化操作。其中tp_new相当于c++中的new,tp_init相当于构造函数。

3.2 对象行为

PyTypeObject定义了大量的函数指针,这些指针最终指向某个函数或null,可以被视为类型对象中所定义的操作,而这些操作直接决定一个对象所表现出的行为。

比如其中的tp_hash:指明该类型的对象如何生成hash值,tp_new 指明该类型对象如何申请内存空间,tp_init指明该类型对象如何初始化。

在这里还要说说三组非常重要的操作族:

  •     PyNumberMethods *tp_as_number:定义一个数值对象应该支持的操作,比如int
  •     PySequenceMethods *tp_as_sequence:定义一个序列对象应该支持的操作,比如list
  •     PyMappingMethods *tp_as_mapping;定义一个关联对象应该支持的操作,比如dict

从PyTypeObject的设计可以看出对于一种对象来说,可以同时包含有上面三种操作族,也就是说,一个对象可以表现出数值对象的特性,也可以表现出序列和关联对象的特性,举例:

>>> class A(int):
    def __getitem__(self, item):
        return item + str(self)

>>> a = A(1)
>>> b = A(2)
>>> print(a+b)
3
>>> a['key']
'key1'
>>> 

A是从int继承出来的,应该就是一个数值对象,但是a['key']却表现出来关联对象才有的特性,这是因为通过重写__getitem__这个特殊方法(可以视为指定了A在python内部对应的PyTypeObject对象的tp_as_mapping操作)。

 

 

3.3 类型的类型

在PyTypeObject定义的最开始,我们可以发现PyObject_VAR_HEAD(定义所有可变大小容器对象的初始段),这意味着python中的类型也是一个对象,那么类型对象的类型是什么呢?这个问题十分重要,对于其他对象,可以通过与其关联的类型对象确定其类型,答案是PyType_Type,先看看它的结构:

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

PyType_Type是python中至关重要的对象,所有自定义对象都是通过它创建的,它就是type元类。

以PyInt_Type为例,可以清晰看出一般对象与PyType_Type的关系:

PyTypeObject PyInt_Type = {
    PyObject_HEAD_INIT(&PyType_Type, 0)
    "int",                                     /* tp_name */
    sizeof(PyIntObject),                    
、       ........
}

python底层设计:对象设计思想_第3张图片

 

4.对象的多态/动态语言的原理

在python中创建一个对象,比如创建PyIntObject对象时,会分配内存,进行初始化,然后使用PyObject*来维护对象,而不是PyIntObject*。其他对象也是如此,所以在python内部的各个函数之间传递的都是一种范型指针PyObject*,我们不能知道指针所指的究竟是哪种类型,只有通过指针所指对象的ob_type域动态确定,正是通过这个域,python实现了多态机制,这也解释了python为什么是动态语言

 

5.计数引用

python通过对一个对象的引用计数的管理来维护对象在内存中存在与否,每个对象都有一个ob_refcnt变量,这个变量维护着该对象的引用计数,从而决定该对象的创建与消亡。在python中,主要是通过Py_INCREF(op) 和Py_DECREF(op)   两个宏来增加和减少计数。

#define _Py_Dealloc(op) (                               \
    _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA          \
    (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
#endif
#endif /* !Py_TRACE_REFS */

#define Py_INCREF(op) (                         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
    ((PyObject *)(op))->ob_refcnt++)

#define  Py_DECREF(op)                                   \
    do {                                                \
        PyObject *_py_decref_tmp = (PyObject *)(op);    \
        if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
        --(_py_decref_tmp)->ob_refcnt != 0)             \
            _Py_CHECK_REFCNT(_py_decref_tmp)            \
        else                                            \
            _Py_Dealloc(_py_decref_tmp);                \
    } while (0)
#define Py_CLEAR(op)                            \
    do {                                        \
        PyObject *_py_tmp = (PyObject *)(op);   \
        if (_py_tmp != NULL) {                  \
            (op) = NULL;                        \
            Py_DECREF(_py_tmp);                 \
        }                                       \
    } while (0)

/* Macros to use in case the object pointer may be NULL: */
#define Py_XINCREF(op)                                \
    do {                                              \
        PyObject *_py_xincref_tmp = (PyObject *)(op); \
        if (_py_xincref_tmp != NULL)                  \
            Py_INCREF(_py_xincref_tmp);               \
    } while (0)

#define Py_XDECREF(op)                                \
    do {                                              \
        PyObject *_py_xdecref_tmp = (PyObject *)(op); \
        if (_py_xdecref_tmp != NULL)                  \
            Py_DECREF(_py_xdecref_tmp);               \
    } while (0)

从源码可以知道,当计数减到0时,Py_DECREF将调用_Py_Dealloc来释放内存资源,这个析构动作是通过对象对应的类型对象中定义的一个函数指针tp_dealloc来实现。

另外,频繁的申请和释放内存对执行效率的影响非常大,所以在python大量采用内存对象池来避免频繁的申请和释放内存空间,因此在析构时,通常是将对象占用的空间归还到内存池中。

 

 

 

 

你可能感兴趣的:(Python)