Python对象

众所周知,Python是一门面向对象的语言,在Python无论是数值、字符串、函数亦或是类型、类,都是对象。
对象是在上分配的结构,我们定义的所有变量、函数等,都存储于堆内存,而变量名、函数名则是一个存储于中、指向堆中具体结构的引用。

  • 堆:主要负责存储Python运行时的所有对象实体(也就是Python对象的所有属性数据),例如在执行s='Hello Word'这条语句时,Python会为'Hello Word'这个字符串创建一个PyASCIIObject对象实体,并将其存储到内存中。
  • 栈:又叫数据栈或值栈,它主要负责保存对堆中Python对象的引用,例如在执行s='Hello Word'这条语句时,Python会将'Hello Word'这个字符串对象实体所处的内存地址压入,而不是将'Hello Word'这个字符串值压入栈。
    堆和栈

要想深入学习Python,首先需要知道Python对象的定义。

Python对象定义

Python对象PyObejct

我们通常说的Python都是指CPython,底层由C语言实现,源码地址:cpython [GitHub]
Python对象的定义位于Include/object.h,是一个名为PyObject的结构体:

/* 定义指针以支持所有活动堆对象的双链表  */
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;

/* 实际上没有任何东西被声明为PyObject,但每个指向Python对象的指针都可以转换为PyObject*。这是手动建立的继承。
 * 同样,每个指向可变大小Python对象的指针也可以转换为PyVarObject*。
 */
typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;    // 引用计数
    PyTypeObject *ob_type;  // 类型对象指针,定义Python对象类型
} PyObject;

Python中的所有对象都继承自PyObejct,PyObject包含一个用于垃圾回收的双向链表,一个引用计数变量ob_refcnt和 一个类型对象指针ob_type

可变对象PyVarObject

从PyObejct的注释中,我们可以看到这样一句:每个指向可变大小Python对象的指针也可以转换为PyVarObject*(可变大小的Python对象会在下文中解释)。PyVarObejct就是在PyObject的基础上多了一个ob_size字段,用于存储元素个数:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* 可变部分中的元素数量 */
} PyVarObject;

Python类型对象PyTypeObject

在PyObject结构中,还有一个类型对象指针ob_type,用于表示Python对象是什么类型,定义Python对象类型的是一个PyTypeObject接口体

typedef struct _typeobject PyTypeObject;

实际定义是位于Include/cpython/object.h_typeobject

struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* 用于打印,格式为"." */
    Py_ssize_t tp_basicsize, tp_itemsize; /* 用于分配大小 */

    /* 实现标准操作的方法 */
    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* Python 2中的tp_compare 或 Python 3中的tp_reserved */
    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;

    ...
};

在这个类型对象中,不仅包含了对象的类型,还包含了如分配内存大小、对象标准操作等信息,主要分为:

  • tp_name:类型名,Python内部使用
  • 创建该类型对象时分配的空间大小信息,即tp_basicsizetp_itemsize
  • 与该类型对象相关的操作信息
  • 一些对象属性

以Python中的int类型为例,int类型对象的定义如下:

PyTypeObject PyLong_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",                                      /* tp_name */
    offsetof(PyLongObject, ob_digit),           /* tp_basicsize */
    sizeof(digit),                              /* tp_itemsize */
    ...
};

Python类型的类型PyType_Type

从PyObject的定义中我们知道,每个对象的ob_type都要指向一个具体的类型对象,比如一个数值型对象100,它的ob_type会指向int类型对象PyLong_Type

PyTypeObject结构体第一行是一个PyObject_VAR_HEAD宏,查看宏定义可知PyTypeObject是一个变长对象

/ * PyObject VAR HEAD定义了所有大小可变的容器对象的初始段。
  * 最后声明了一个只有1个元素的数组,但是已经分配了足够的空间,使得数组实际上有足够的空间容纳ob_size的元素。
  * 注意,ob size是一个元素计数,不一定是字节计数。 
*/
#define PyObject_VAR_HEAD      PyVarObject ob_base;

也就是说,归根结底类型对象也是一个对象,也有ob_type属性,那PyLong_Typeob_type是什么呢?
回到PyLong_Type的定义,第一行PyVarObject_HEAD_INIT(&PyType_Type, 0),查看对应的宏定义

// Include/object.h
#ifdef Py_TRACE_REFS
...
#define _PyObject_EXTRA_INIT 0, 0,
#else
...
#  define _PyObject_EXTRA_INIT
#endif

#define PyObject_HEAD_INIT(type)        \
    { _PyObject_EXTRA_INIT              \
    1, type },

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

由以上关系可以知道,PyVarObject_HEAD_INIT(&PyType_Type, 0) = { { _PyObject_EXTRA_INIT 1, &PyType_Type } 0},将其代入PyObject_VAR_HEAD,得到一个变长对象:

/ * PyObject_VAR_HEAD,实际是PyVarObject */
{
    / * ob_base,即PyObject */
    {
        _PyObject_EXTRA_INIT  // _PyObject_HEAD_EXTRA
        1;    // ob_refcnt=1
        &PyType_Type;  // ob_type=&PyType_Type
    } 
    0;  // ob_size=0
}

这样看就很明确了,PyLong_Type的类型就是PyType_Typ,同理可知,Python类型对象的类型就是PyType_Type,而PyType_Type对象的类型是它本身

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */
    ...
};
对象的类型关系

Python对象分类

从上述内容中,我们知道了对象和对象类型的定义,那么根据定义,对象可以有以下两种分类

按大小是否可变分类

Python对象定义有PyObjectPyVarObject,因此,根据对象大小是否可变的区别,Python对象可以划分为可变对象(变长对象)不可变对象(定长对象)

  • 定长对象:变量发生改变时,对象大小是不变的,改变的是引用
    比如,s = 'hello ',变量s指向一个字符串(对象a),当s发生改变:s += ' world',变量s指向了一个新的字符串(对象b):
>>> s = 'hello '
>>> id(s)
17123904
>>> s += 'world'
>>> id(s)
8213552

原本的对象a大小并没有改变,只是s引用的对象改变了。这里的对象a、对象b就是定长对象

  • 变长对象:变量发生改变时,变量的引用不变,改变的是对象的内容
    比如,l = [1, 2, 3],变量l指向一个数组(对象a),当l发生改变:l.append(4)l += [5]
>>> l = [1, 2, 3]
>>> id(l)
7686744
>>> l.append(4)
>>> id(l)
7686744
>>> l += [5]
>>> id(l)
7686744

可以看到,变量l仍然指向对象a,只是对象a的内容发生了改变,数据量变大了。这里的对象a就是变长对象

由于存在以上特性,所以使用这两种对象还会带来一种区别:
声明s2 = s,修改s的值:s = 'new string',s2的值不会一起改变,因为只是s指向了一个新的对象,s2指向的旧对象的值并没有发生改变
声明l2 = l,修改l的值:l.append(6),此时l2的值会一起改变,因为l和l2指向的是同一个对象,而该对象的内容被l修改了

此外,对于字符串对象,Python还有一套内存复用机制,如果两个字符串变量值相同,那它们将共用同一个对象:

>>> s1 = 'Hello'
>>> s2 = 'Hello'
>>> id(s1)
25512608
>>> id(s2)
25512608
>>>

对于数值型对象,Python会默认创建0~28 以内的整数对象,也就是0 ~ 256之间的数值对象是共用的:

>>> n1=0
>>> n2=0
>>> id(n1)
2038522960
>>> id(n2)
2038522960
>>>
>>> n1 = 256
>>> n2 = 256
>>> id(n1)
2038527056
>>> id(n2)
2038527056
>>>
>>> n1 = 257
>>> n2 = 257
>>> id(n1)
19586496
>>> id(n2)
25412160

按数据类型分类

按照Python数据类型,对象可分为以下几类:

  • Fundamental 对象:类型对象
  • Numberic 对象:数值对象
  • Sequence 对象:容纳其他对象的序列集合对象
  • Mapping 对象:键值对关联的字典对象
  • Internal 对象:Python虚拟机内部使用的对象
对象分类

对象的创建

Python创建对象有两种方式,泛型API和和类型相关的API

泛型API(Abstract Object Layer)

这类API通常以PyObject_xxx的形式命名,可以应用在任意Python对象上,如:

  • PyObject_New(type, typeobj) 为给定类型的对象分配内存,并初始化该对象的一部分
  • PyObject_NewVar(type, typeobj, n) 为可变大小的对象分配内存并分配n个元素的空间
  • PyObject_Init(op, typeobj) 不分配内存,接受指向新对象(由任意分配器分配)的指针,而不是“类型”形参,并初始化其对象头字段
  • PyObject_InitVar(op, typeobj, n) 同上

使用PyObjecg_New创建一个数值型对象:

PyObject* longObj = PyObject_New(PyObject, &PyLong_Type);

与类型相关API(Concrete Object Layer)

这类API通常只能作用于一种类型的对象上,如:

  • PyObject * PyLong_FromLong(long ival) 通过长整型创建新对象
  • PyObject * PyLong_FromUnsignedLong(unsigned long ival) 通过无符号长整型创建新对象
  • ...

使用PyLong_FromLong创建一个数值型对象:

PyObject* longObj = PyLong_FromLong(10)

对象的多态性

在我们使用Python声明变量的时候,并不需要为变量指派类型,在给变量赋值的时候,可以赋值任意类型数据,如:

a = 1
a = 'string'
a = [1, 2, 3]

从Python对象的定义我们已经可以知晓造成这个特点的原因了,Python创建对象时,会分配内存进行初始化,然后Python内部通过PyObject*变量来维护这个对象,所以在Python内部各函数直接传递的都是一种泛型指针PyObject*,这个指针所指向的对象类型是不固定的,只能通过所指对象的ob_type属性动态进行判断,而Python正是通过ob_type实现了多态机制

引用计数

Python在管理维护对象时,通过引用计数来判断内存中的对象是否需要被销毁,Python中所有事物都是对象,所有对象都有引用计数ob_refcnt
当一个对象的引用计数减少到0之后,Python将会释放该对象所占用的内存和系统资源。
但这并不意味着最终一定会释放内存空间,因为频繁申请释放内存会大大降低Python的执行效率,因此Python中采用了内存对象池的技术,是的对象释放的空间会还给内存池,而不是直接释放,后续需要申请空间时,优先从内存对象池中获取。

你可能感兴趣的:(Python对象)