这是一个链接列表,还是一个数组?我搜寻了一下,只发现有人在猜测。我的 C 语言知识不足以查看源代码。
这是一个链接列表,还是一个数组?我搜寻了一下,只发现有人在猜测。我的 C 语言知识不足以查看源代码。
这段C代码实际上非常简单。展开一个宏并剪除一些不相关的注释后,基本结构在listobject.h
中定义列表为:
typedef struct {
PyObject_HEAD
Py_ssize_t ob_size;
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
*/
Py_ssize_t allocated;
} PyListObject;
PyObject_HEAD
包含一个引用计数和类型标识符。因此,它是一个超配的向量/数组。当这种数组已经满了需要调整大小时,其代码位于 listobject.c
中。它实际上并没有将数组扩大一倍,而是通过分配内存来增长。
new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;
每次增加的容量是newsize
,其中newsize
是请求的大小(不一定是allocated+1
,因为你可以通过一次添加任意数量的元素而不是一个一个地append
来extend
)。
另请参见Python FAQ。
array
模块或NumPy。 - Fred Foo这是一个动态数组。实际证明:索引(当然有极小的差异(0.0013微秒!))花费相同的时间,无论索引是什么:
...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop
...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop
如果IronPython或Jython使用了链表,我会感到非常惊讶-它们将破坏许多广泛使用的库的性能,这些库建立在列表是动态数组的假设之上。
List object C structure
A list object in CPython is represented by the following C structure.
ob_item
is a list of pointers to the list elements. allocated is the number of slots allocated in memory.
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; } PyListObject;
It is important to notice the difference between allocated slots and the size of the list. The size of a list is the same as
len(l)
. The number of allocated slots is what has been allocated in memory. Often, you will see that allocated can be greater than size. This is to avoid needing callingrealloc
each time a new elements is appended to the list.
...
追加我们向列表中追加一个整数:l.append(1)
。会发生什么?
接下来,我们再添加一个元素:l.append(2)
。调用了list_resize
,n+1=2,但由于已分配的大小为4,因此无需再分配更多内存。当我们再添加两个整数时同样也是如此:l.append(3)
、l.append(4)
。以下图表显示目前为止的情况。
...
我来翻译一下。这段内容讲述了如何在编程中插入一个新的整数(5)到指定位置(1)。具体操作是使用代码l.insert(1,5)
,并且查看内部发生的事情。...
弹出当您弹出最后一个元素:l.pop()
,会调用listpop()
。在listpop()
内部调用了list_resize
,如果新的大小小于分配的一半,则会缩小列表。
您可以观察到槽4仍然指向整数,但重要的是列表的大小现在为4。
让我们再弹出一个元素。在list_resize()
中,size - 1 = 4-1 = 3小于分配的槽数的一半,因此将列表缩小到6个槽,并且列表的新大小现在为3。
您可以观察到槽3和4仍然指向某些整数,但重要的是列表的大小现在为3。
...
O(1)
是一个相当普遍和有效的假设,因此没有实现会冒险打破它。 - user395760在CPython中,列表是指针数组。Python的其他实现可能选择以不同的方式存储它们。
PyObject **ob_item;
将无法工作,因为它必须指向旧元素的旧内存空间和新元素的新内存空间。 - Kilian Batznerlist_one = [1,2,3,4]
my_list = list_one
#my_list: [1,2,3,4]
my_list.append("new")
#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']
my_list
,但是我们的主列表已经改变。这意味着该列表不是作为副本分配,而是作为其引用分配。我发现这篇文章非常有帮助,可以理解如何使用Python代码实现列表。
class OhMyList:
def __init__(self):
self.length = 0
self.capacity = 8
self.array = (self.capacity * ctypes.py_object)()
def append(self, item):
if self.length == self.capacity:
self._resize(self.capacity*2)
self.array[self.length] = item
self.length += 1
def _resize(self, new_cap):
new_arr = (new_cap * ctypes.py_object)()
for idx in range(self.length):
new_arr[idx] = self.array[idx]
self.array = new_arr
self.capacity = new_cap
def __len__(self):
return self.length
def __getitem__(self, idx):
return self.array[idx]