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

340 阅读4分钟

结论放前面 :

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

      

      

      前几天做了好几个实验,探究了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是指向列表对象的指针数组;

  

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

比如,假设 字符串45ijk存在内存 42117360 开始的一段内存里面,我现在执行 a[1] = "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更新------------------------------------