序列切片的Python恒等式

5vf7fwbs  于 2022-11-19  发布在  Python
关注(0)|答案(1)|浏览(110)

我注意到了某些序列类型的切片的“标识”,即id()返回的值,我无法理解。我看到它带有列表和字符串,这使我认为它与CPython中序列或切片的实现有关。
3. Data model - Python 3.11.0 documentation中所述:
CPython实现详细信息:对于CPython,id(x)是存储x的内存地址。

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> l[3]
3
>>> l[7]
7
>>>
>>> id(l)
1931973192256
>>> id(l[3])
1931941276016
>>> id(l[7])
1931941276144

到目前为止,没有什么异常,因为我希望看到列表对象的内存地址与从列表返回的单个元素的内存地址不同。然而,当查看列表的切片时,内存地址对我来说没有意义:

>>> l[2:]
[2, 3, 4, 5, 6, 7, 8, 9]
>>> l[5:]
[5, 6, 7, 8, 9]
>>> l[3:9]
[3, 4, 5, 6, 7, 8]
>>> l[:-6]
[0, 1, 2, 3]
>>>
>>> id(l[2:])
2813785568448
>>> id(l[5:])
2813784049664
>>> id(l[3:9])
2813784049664
>>> id(l[:-6])
2813784049664
>>> id(l[2:])
2813784049664

在列表的第一个切片之后,id()返回相同的值,而不管切片之后的外观如何。
我的问题是,当一个列表被切片时,id()返回的内存地址到底是什么?接下来,为什么第一个和第二个切片的标识不同,而后面的切片是相同的,包括第一个切片?

brqmpdu1

brqmpdu11#

这种行为是由Cpython优化引起的。

>>> l[2:]

list always creates a new list进行切片。由于分配和释放列表对象是一个开销很大的操作,python解释器会跟踪free_list,它是指向PyListObject的指针数组。

/* Empty list reuse scheme to save calls to malloc and free */
#ifndef PyList_MAXFREELIST
#  define PyList_MAXFREELIST 80
#endif

struct _Py_list_state {
#if PyList_MAXFREELIST > 0
    PyListObject *free_list[PyList_MAXFREELIST];
    int numfree;
#endif
};

所以当你请求一个新的列表时(记住分片也会创建一个新的列表),它会从这个空闲列表中得到服务(如果空闲列表可用)。

if (PyList_MAXFREELIST && state->numfree) {
        state->numfree--;
        op = state->free_list[state->numfree];
        OBJECT_STAT_INC(from_freelist);
        _Py_NewReference((PyObject *)op);
}

因为你要立即丢弃list对象,python解释器调用list_dealloc,它将恢复这个free_list

if (state->numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) {
        state->free_list[state->numfree++] = op;
        OBJECT_STAT_INC(to_freelist);
}

下次进行分片时,实际上是从free_list获取对同一列表对象的引用。

相关问题