Python源码剖析阅读笔记 :第一章:Python对象初探

Python对象初探

对象是python中的核心概念,Python中一切皆对象。一种内置数据类型也是一种对象,我们称之为内置类型对象(int,string,list等)。这些类型对象实现了面向对象中‘类’的概念。使用时,就可以实例化出这些内置类型对象。

第一章只需要了解python对象是如何被c实现的。python对象整个体系在第二章会有完整讲解。

1.1python内的对象

在python中,对象就是为C中的结构体在堆上申请的一块内存,一般来说,对象是不能被静态初始化的,并且也不能在栈空间上生存。唯一的例外就是类型对象,Python中所有的内建的类型对象都是被静态初始化的。

python对象创建后,内存中的大小是不变的。如果那些需要容纳可变长度数据的对象(list,dict等可变类型的实例化对象)
,只能维护一个指向一块可变大小的内存区域的指针。这样会使维护对象的工作变得简单。从对数据的操作变成了对指针的操作,工作由繁化简了。

1.1.1对象机制的基石——PyObject

在python中,所有的东西都是对象,而所有的对象都拥有一些相同的内容,这些内容在pyobject中定义。
[object.h]

typedef struct_object{
      PyObject_HEAD
};

这个struct就是对象机制中的基石,从代码中可以看到,Python对象的秘密都隐藏在PyObject_HEAD这个宏中。

#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

实际发布python中,PyObject的定义非常简单:

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

ob_refcnt是与python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制。另一个是指向结构体的指针*ob_type,用来说明创建的对象是什么类型的一个类型对象。

定长对象和变长对象

整数对象的特殊信息是一个C中的整型变量,无论这个整数对象的值有多大,都可以保存在这个整形变量(ob_ival)中。但是并不是所有对象都可以这么定义。比如字符串。所以需要添加‘变量的个数’这一变量(ob_size)来控制个数。

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

我们把整数对象这样不包含可变长度数据的对象成为‘定长对象’,而字符串对象这样包含可变长度数据的对象成为‘变长对象’。在python内部,每一个对象都拥有相同的对象头部。这就使得在python中,对对象的引用变得非常统一,我们只需要一个PyObject*指针就可以引用任意一个对象。

类型对象

上面大概的表述,python所有对象共有信息的定义。python对象的主要部分。当内存中分配空间,创建对象的时候,必须要知道申请多大的空间。然而它是不确定的。

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" 调试时使用输出格式<module>.<name> */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation python对象创建时内存大小的信息 */
     ....

1.2.1对象的创建

有两种方式来创建python对象,第一种:python C API来创建,第二种:类型对象PYInet_Type。
Python的C API分成两类,一类成为范型的API,或者称为AOL(abstract object layer)。这类API都形如:PyObject_***。可以应用任何python对象身上。对于创建一个整数对象,我们可以采用如下表达式:PyObject* intObj = PyObject_New(PyObject, &PyInt_Type)

另一类是与类型相关的API,COL(Concrete Object layer)。这类通常只能作用在某一种类型的对象上,对于每一种内建对象,都会有对应的API。
例子:PyObject *intObj = PyInt_FromLong(10)创建了一个整数为10的变量。

1.2.2对象的行为

在PyTypeObject中定义了大量的指针,这些函数指针最终都会指向某个函数,或是指向NULL。这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现的行为。

#ifdef Py_LIMITED_API
typedef struct _typeobject PyTypeObject; /* opaque */
#else
typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    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;

在这些操作信息中,有三组非常重要的操作族,他们是

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;    /*数值对象 */
    PySequenceMethods *tp_as_sequence; /*序列对象 */
    PyMappingMethods *tp_as_mapping;      /* 关联对象*/

对于一种类型来说,它完全可以同时定义三个函数中的所有操作。换句话说,一个对象可以即表现出数值对象的特性,也可以表现出关联对象的特性。举个例子:

class MyInt(int):
   def __getitem__(self, key):
        return key + str(self)

a = MyInt(1)

b = MyInt(2)

print(a + b)
print(a['key'])

结果:

看上去a['key']这样的操作时一个类似于dict这样的对象才会支持的操作。从int继承出来的Myint应该自然就是一个数值对象,重写上面的方法之后,可以视为指定MyInt在Python内部对应的PyTypeObject对象的tp_as_mapping.mp_subscript操作。最终这个Myint的实例对象可以“表现”得像一个关联对象。本质上就是在于PyTypeObject中允许一种类型同事指定三种不同行为的对象。

1.2.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 */
    (destructor)type_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    (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,         /* 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 */
};

PyType_Type在python的类型机制中是一个至关重要的对象,所有用户自定义的class所对应的PyTypeObject对象都是通过这个对象创建的。图中显示了一般的PyType_Object和PyType_Type的关系:

<type.type>是python内部的PyType_Type,它是所有class的class,所以它在Python中被称为metaclass。

Py-nt_Type是怎么和PyType_Type联系上的呢。前面提到,python中,每一个对象都将自己的引用计数、类型信息保存在开始的部分中。为了方便对内存的初始化,python提供了几个宏。

1.3 Python对象的多态性

Python 创建一个对象比如 PyLongObject 时,会分配内存进行初始化,然后 Python 内部会用 PyObject* 变量来维护这个对象,其他对象也与此类似
所以在 Python 内部各个函数之间传递的都是一种范型指针 PyObject* 我们不知道这个指针所指的对象是什么类型,只能通过所指对象的 ob_type 域 动态进行判断,而 Python 正是通过 ob_type 实现了多态机制
比如:

void Print(PyObject* object)
{
      object->ob_type->tp_print(object);
}

传进来的指针可以是int型也可以是string类型,他们都会调用他们相应的类型对象中定义的输出操作。这就是多态性的展现。

1.4引用计数

Python 通过引用计数来管理维护对象在内存中的存在与否
Python 中的每个东西都是一个对象, 都有ob_refcnt 变量,这个变量维护对象的引用计数,从而最终决定该对象的创建与销毁
在 Python 中,主要通过 Py_INCREF(op)Py_DECREF(op) 这两个宏 来增加和减少对一个对象的引用计数。当一个对象的引用计数减少到 0 之后, Py_DECREF将调用该对象的tp_dealloc来释放对象所占用的内存和系统资源;
但这并不意味着最终一定会调用 free 释放内存空间。因为频繁的申请、释放内存会大大降低 Python 的执行效率。因此 Python 中大量采用了内存对象池的技术,使得对象释放的空间归还给内存池而不是直接free,后续使用可先从对象池中获取

// Include/object.h
#define _Py_NewReference(op) (                          \
    _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA               \
    Py_REFCNT(op) = 1)

#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)

1.5Python对象的分类

大致分为5类。

  • Fundanmental对象:类型对象
  • Numeric对象:数值对象
  • Sequence对象:容纳其他对象的序列集合对象
  • Mapping对象:具有映射关系的对象
  • Internal对象: Python虚拟机在运行时内部使用的对象

    这些是对python对象的一些基本了解。
posted @ 2020-06-16 17:38  L1m1t  阅读(244)  评论(0编辑  收藏  举报