Python列表是动态数组实现的序列类型,它的底层内存结构并非直接存储元素对象,而是存储指向元素对象的指针,同时会预分配额外的内存空间来减少扩容时的开销。

Python列表的底层内存结构
Python的列表对象在C语言层面的实现是PyListObject结构体,核心包含三个部分:
- 指向指针数组的指针,数组中的每个元素都是
PyObject*类型,也就是指向实际存储对象的指针 - 列表当前已存储的元素数量,即
len(list)返回的值 - 列表当前分配的内存空间可容纳的元素数量,也就是预分配的容量
当我们创建一个空列表时,Python会先分配一个较小的初始容量,当元素数量超过当前容量时,列表会触发扩容机制:通常会分配原有容量1.125倍左右的新内存空间,把原有指针拷贝到新空间,再释放旧空间。这种预分配机制能减少频繁申请内存的开销,但也会带来一定的内存冗余。
append方法的实现逻辑
append方法的作用是向列表末尾添加一个元素,它的实现逻辑很简单:
- 检查当前列表的元素数量是否已经达到预分配的容量,如果达到就先触发扩容
- 把新元素的指针放到指针数组的末尾位置
- 列表的元素数量加1
下面是模拟append逻辑的简化代码示例:
# 模拟append的核心逻辑
class SimpleList:
def __init__(self):
self.capacity = 4 # 初始预分配容量
self.size = 0
self.pointers = [None] * self.capacity
def append(self, item):
# 容量不足时扩容
if self.size >= self.capacity:
new_capacity = int(self.capacity * 1.125) + 1
new_pointers = [None] * new_capacity
for i in range(self.size):
new_pointers[i] = self.pointers[i]
self.pointers = new_pointers
self.capacity = new_capacity
# 添加元素指针
self.pointers[self.size] = item
self.size += 1
# 测试
sl = SimpleList()
sl.append(1)
sl.append(2)
print(sl.size) # 输出2
extend方法的实现逻辑
extend方法的作用是向列表末尾添加另一个可迭代对象的所有元素,它的实现逻辑和append有明显区别:
- 先计算要添加的可迭代对象的长度,判断添加后总元素数是否超过当前容量,如果超过就一次性扩容到足够的大小,避免多次扩容
- 遍历可迭代对象,把每个元素的指针依次放到指针数组的末尾
- 列表的元素数量加上可迭代对象的长度
下面是模拟extend逻辑的简化代码示例:
# 模拟extend的核心逻辑
class SimpleList:
def __init__(self):
self.capacity = 4
self.size = 0
self.pointers = [None] * self.capacity
def _resize(self, new_capacity):
new_pointers = [None] * new_capacity
for i in range(self.size):
new_pointers[i] = self.pointers[i]
self.pointers = new_pointers
self.capacity = new_capacity
def extend(self, iterable):
# 计算要添加的元素数量
add_len = len(iterable)
total_len = self.size + add_len
# 容量不足时一次性扩容
if total_len > self.capacity:
self._resize(total_len)
# 添加所有元素指针
for item in iterable:
self.pointers[self.size] = item
self.size += 1
# 测试
sl = SimpleList()
sl.extend([1, 2, 3])
print(sl.size) # 输出3
append与extend性能对比
从实现逻辑可以看出,两者的性能差异主要体现在两个场景:
添加单个元素时
添加单个元素时append的性能更好,因为extend需要先调用len获取可迭代对象长度,还要走遍历可迭代对象的流程,额外开销更多。我们可以用timeit模块测试:
import timeit
def test_append():
lst = []
for i in range(1000):
lst.append(i)
def test_extend_single():
lst = []
for i in range(1000):
lst.extend([i])
# 每个测试运行10000次
append_time = timeit.timeit(test_append, number=10000)
extend_time = timeit.timeit(test_extend_single, number=10000)
print(f"append耗时: {append_time:.4f}秒")
print(f"extend耗时: {extend_time:.4f}秒")
测试结果通常会显示append的耗时比extend少30%左右。
添加多个元素时
当需要添加多个元素时,extend的性能优势会非常明显。因为append添加多个元素需要多次调用,每次调用都可能触发扩容,而extend会一次性计算所需容量,只触发一次扩容,同时减少了函数调用的开销。测试代码如下:
import timeit
def test_append_multi():
lst = []
add_lst = list(range(1000))
for item in add_lst:
lst.append(item)
def test_extend_multi():
lst = []
add_lst = list(range(1000))
lst.extend(add_lst)
append_time = timeit.timeit(test_append_multi, number=10000)
extend_time = timeit.timeit(test_extend_multi, number=10000)
print(f"多次append耗时: {append_time:.4f}秒")
print(f"一次extend耗时: {extend_time:.4f}秒")
测试结果通常会显示extend的耗时只有多次append的1/3甚至更少。
使用场景建议
根据两者的特性和性能差异,我们可以按照以下原则选择:
- 如果只需要添加一个元素,优先使用
append,代码更简洁,性能也更好 - 如果需要添加多个元素,或者添加的是一个可迭代对象的所有元素,优先使用
extend,能大幅减少性能开销 - 不要为了添加多个元素多次调用
append,这种写法既冗余又低效
注意:append会把整个可迭代对象作为一个元素添加到列表末尾,而extend会把可迭代对象的每个元素分别添加,两者的功能完全不同,选择时要先明确需求。