Blog Post

最终总结 python中 list 到底是怎么实现的,内存里面是怎么存放的

结论放前面 :

list 就是个指针数组,指针数组里面对应存放了每个元素所在的地址

又来推一个 python 字典的剖析

前几天做了好几个实验,探究了python lis t的一些东西,想看我怎么一步步得出了结论的可以看看下面的

python修改列表的值时的地址变化,一个蛮有意思的小实验

python 列表的 元素id 探寻,地址到底是怎么分配的

探究 python 是不是用了一个整数缓冲池缓存了指定范围的整数

这几篇都没解决我的终极问题。list 在内存里面是怎么存放的,原来只测试了整数以为是顺序挨个挨个存的(当时id是连着的),列表放的就是元素的值,每次运行比如说 1 的 id 都是一样的,运行无数次都不变,最后才搞清楚 python用了一个整数缓冲池缓存了指定范围的整数 (详见上面链接),后面经过一系列的改变值,列表里面有字符,有字符串,有列表,一顿操作之后发现,原来的的理解就是错的离谱。

没弄明白我睡不着啊,所以又耐着性子把 GitHub上的 list 实现的开源 c 代码看了一遍。这回的理解应该是没问题了。

a = [1,2,'a','dfg',[3,4]]

print(id(a))

print("原始的各元素id:")

for i in range(len(a)):

print(id(a[i]))

for i in a[4]:

print(id(i))

输出

185607304 原始的各元素id: 8791494284320 8791494284352 34249056 42248432 185607176 8791494284384 8791494284416

我Visio 花的图应该还可以,基本能解释清楚了

我的得出的结论:在Python中,列表是一个动态的指针数组,id(list)返回的是这个指针数组所在的地址示例里面的 185607304,这个指针数组里面对应存放了每个元素所在的地址,原来的列表是顺序存储的这个结论也还是对的,数组就是顺序存储的嘛。也只有这个结论是通用正确的

GitHub上的 list 实现的开源 c 代码

打不开网址的我把代码拿过来了,删掉注释就剩下下面的

#ifndef Py_LISTOBJECT_H

#define Py_LISTOBJECT_H

#ifdef __cplusplus

extern "C" {

#endif

#ifndef Py_LIMITED_API

typedef struct {

PyObject_VAR_HEAD

PyObject **ob_item;

Py_ssize_t allocated;

} PyListObject;

#endif

PyAPI_DATA(PyTypeObject) PyList_Type;

PyAPI_DATA(PyTypeObject) PyListIter_Type;

PyAPI_DATA(PyTypeObject) PyListRevIter_Type;

PyAPI_DATA(PyTypeObject) PySortWrapper_Type;

#define PyList_Check(op) \

PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)

#define PyList_CheckExact(op) (Py_TYPE(op) == &PyList_Type)

PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size);

PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *);

PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t);

PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *);

PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *);

PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *);

PyAPI_FUNC(PyObject *) PyList_GetSlice(PyObject *, Py_ssize_t, Py_ssize_t);

PyAPI_FUNC(int) PyList_SetSlice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);

PyAPI_FUNC(int) PyList_Sort(PyObject *);

PyAPI_FUNC(int) PyList_Reverse(PyObject *);

PyAPI_FUNC(PyObject *) PyList_AsTuple(PyObject *);

#ifndef Py_LIMITED_API

PyAPI_FUNC(PyObject *) _PyList_Extend(PyListObject *, PyObject *);

PyAPI_FUNC(int) PyList_ClearFreeList(void);

PyAPI_FUNC(void) _PyList_DebugMallocStats(FILE *out);

#endif

#ifndef Py_LIMITED_API

#define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])

#define PyList_SET_ITEM(op, i, v) (((PyListObject *)(op))->ob_item[i] = (v))

#define PyList_GET_SIZE(op) (assert(PyList_Check(op)),Py_SIZE(op))

#define _PyList_ITEMS(op) (((PyListObject *)(op))->ob_item)

#endif

#ifdef __cplusplus

}

#endif

#endif /* !Py_LISTOBJECT_H */

然后真正定义 list 的就这么点

typedef struct {

PyObject_VAR_HEAD

PyObject **ob_item;

Py_ssize_t allocated;

} PyListObject;

ob_item是指向列表对象的指针数组;

allocated是申请内存的槽的个数。用于对指针数组扩内存 这儿的探究见 一、通过getsizeof()计算列表的增长模式

那我已经懂了,修改 list 元素就只是改变指针数组的对应位置值,

比如,假设 字符串45ijk存在内存 42117360 开始的一段内存里面,我现在执行 a[0] = "45ijk" ,就是做下面操作

----------------------2020.9.15更新------------------------------------

上面提到 list 的源码如下

typedef struct {

PyObject_VAR_HEAD

PyObject **ob_item;

Py_ssize_t allocated;

} PyListObject;

但是今天我再看的时候发现 PyObject_VAR_HEAD 是个啥呢?问题儿童上线 , 从代码来看PyObject_HEAD是一个宏,这个应该熟悉 c 的人都能看出来(没分号结尾又是正确的,那肯定是个宏)。看了下源码,宏中定义的两个属性分别是:

int ob_refcnt;

struct _typeobject *ob_type;

这两个属性是所有Python对象固有的:

ob_refcnt:对象的引用计数,与Python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制

ob_type:用于描述Python对象的类型信息。

----------------------2020.9.15更新------------------------------------