Python列表中增加元素看似简单,但 append、insert、extend 和切片赋值在添加位置、添加数量、时间复杂度和返回值上差异很大。很多问题不是 API 不会用,而是选错方式导致结果不符合预期,甚至出现性能瓶颈。
一、append:末尾追加单个元素
append 把一个对象追加到列表末尾,是最常用的方式。它只添加一个元素,即使参数是列表,也只会把整个列表当成一个元素。- fruits = ['苹果', '香蕉']
- fruits.append('橘子')
- print(fruits) # ['苹果', '香蕉', '橘子']
- fruits.append(42)
- fruits.append([1, 2, 3])
- fruits.append({'key': 'value'})
- print(fruits)
- # ['苹果', '香蕉', '橘子', 42, [1, 2, 3], {'key': 'value'}]
复制代码 append 的关键:append(x) 等价于 lst[len(lst):] = [x],总是在末尾添加一个元素。不要用 append 添加多个元素:- nums = [1, 2, 3]
- nums.append([4, 5])
- print(nums) # [1, 2, 3, [4, 5]]
- print(len(nums)) # 4
复制代码 另外,append 返回 None,不是修改后的列表。写成 nums = nums.append(5) 会让 nums 变成 None。- nums = [1, 2, 3]
- result = nums.append(4)
- print(result) # None
- print(nums) # [1, 2, 3, 4]
复制代码 append 平均时间复杂度为 O(1)。Python 列表采用过度分配策略,不是每次追加都重新申请内存。
二、insert:在指定索引前插入
insert(i, x) 把 x 插入到当前索引 i 的元素之前,插入后 x 的索引为 i,原来的 lst[i:] 向后移动。- fruits = ['苹果', '香蕉', '橘子']
- fruits.insert(1, '葡萄')
- print(fruits) # ['苹果', '葡萄', '香蕉', '橘子']
- fruits.insert(0, '西瓜')
- print(fruits) # ['西瓜', '苹果', '葡萄', '香蕉', '橘子']
复制代码 索引规则:i 超过长度时插到末尾;i 小于 -len(lst) 时插到开头。- lst = [10, 20, 30, 40, 50]
- lst.insert(2, 99)
- print(lst) # [10, 20, 99, 30, 40, 50]
- lst.insert(100, 999)
- print(lst) # [10, 20, 99, 30, 40, 50, 999]
- lst.insert(-100, -999)
- print(lst) # [-999, 10, 20, 99, 30, 40, 50, 999]
复制代码 insert 的时间复杂度是 O(n),因为插入位置后面的元素都要移动。频繁在头部插入时,性能会很差。需要头部频繁插入时,应使用 collections.deque:- from collections import deque
- dq = deque()
- dq.appendleft('a')
- dq.appendleft('b')
- dq.appendleft('c')
- print(list(dq)) # ['c', 'b', 'a']
复制代码
三、extend:批量追加可迭代对象
extend(iterable) 把可迭代对象中的每个元素逐个追加到列表末尾。列表、元组、字符串、range、集合、字典、生成器都可以。- nums = [1, 2, 3]
- nums.extend([4, 5, 6])
- print(nums) # [1, 2, 3, 4, 5, 6]
- nums.extend((7, 8))
- print(nums) # [1, 2, 3, 4, 5, 6, 7, 8]
- nums.extend('abc')
- print(nums) # [1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c']
- nums.extend(range(10, 13))
- print(nums) # [1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c', 10, 11, 12]
复制代码 extend 与 append 的区别:- lst1 = [1, 2, 3]
- lst1.append([4, 5])
- print(lst1) # [1, 2, 3, [4, 5]]
- lst2 = [1, 2, 3]
- lst2.extend([4, 5])
- print(lst2) # [1, 2, 3, 4, 5]
复制代码 extend 原地修改列表;+ 运算符会创建新列表;+= 通常等价于 extend,调用 __iadd__,而 lst = lst + iterable 调用 __add__ 创建新列表。- lst3 = [1, 2, 3]
- lst4 = lst3 + [4, 5]
- print(lst3) # [1, 2, 3]
- print(lst4) # [1, 2, 3, 4, 5]
- lst3.extend([4, 5])
- print(lst3) # [1, 2, 3, 4, 5]
复制代码 性能上,extend 通常比循环 append 快,因为循环在 C 层面完成;但如果是从头创建列表,列表推导式往往更快。
四、切片赋值:最灵活的插入方式
切片赋值可以在任意位置插入任意数量元素,语法是 lst[a:a] = iterable。- nums = [1, 5, 9]
- nums[1:1] = [2, 3, 4]
- print(nums) # [1, 2, 3, 4, 5, 9]
- nums[:0] = [-2, -1, 0]
- print(nums) # [-2, -1, 0, 1, 2, 3, 4, 5, 9]
- nums[len(nums):] = [10, 11, 12]
- print(nums) # [-2, -1, 0, 1, 2, 3, 4, 5, 9, 10, 11, 12]
复制代码 切片插入和 extend 一样接受任何可迭代对象,也可以插入生成器;插入空序列等于什么都不做。- lst = [1, 6]
- lst[1:1] = (2, 3, 4, 5)
- print(lst) # [1, 2, 3, 4, 5, 6]
- lst[1:1] = (x ** 2 for x in range(3))
- print(lst) # [1, 0, 1, 4, 2, 3, 4, 5, 6]
复制代码
五、四种方式对比
功能对比:- goal = [1, 2, 3, 4, 5]
- result1 = [1, 2, 3]
- result1.append(4)
- result1.append(5)
- print(f'append: {result1}')
- result2 = [1, 3, 4, 5]
- result2.insert(1, 2)
- print(f'insert: {result2}')
- result3 = [1, 2]
- result3.extend([3, 4, 5])
- print(f'extend: {result3}')
- result4 = [1, 5]
- result4[1:1] = [2, 3, 4]
- print(f'切片: {result4}')
复制代码 对比表:
方法 | 添加位置 | 添加数量 | 时间复杂度 | 返回值
append(x) | 末尾 | 1个(x本身) | O(1) | None
insert(i,x) | 指定索引i之前 | 1个(x本身) | O(n) | None
extend(it) | 末尾 | N个(展开) | O(k) | None
lst[a:a]=it | 指定位置 | 任意 | O(n) | None
性能方面,尾部追加用 append 循环和 extend 一次性追加,extend 通常更快;头部插入 list.insert(0) 比 deque.appendleft 慢很多。原文示例中 100000 次尾部追加和 10000 次头部插入都显示了明显差距。
六、实战:构建列表与分页累积
用不同方式构建 [0, 1, 4, 9, 16, 25]:- squares1 = []
- for i in range(6):
- squares1.append(i ** 2)
- print(squares1) # [0, 1, 4, 9, 16, 25]
- squares2 = []
- squares2.extend(i ** 2 for i in range(6))
- print(squares2) # [0, 1, 4, 9, 16, 25]
- squares5 = [i ** 2 for i in range(6)]
- print(squares5) # [0, 1, 4, 9, 16, 25]
复制代码 Builder 模式可以用 append 和 extend 逐步构建列表:- class ListBuilder:
- '''用append模式逐步构建列表——类似StringBuilder'''
- def __init__(self):
- self._items = []
- def add(self, item):
- self._items.append(item)
- return self
- def add_all(self, items):
- self._items.extend(items)
- return self
- def build(self):
- return self._items[:]
- result = (ListBuilder()
- .add('表头')
- .add_all(range(1, 5))
- .add('表尾')
- .build())
- print(result) # ['表头', 1, 2, 3, 4, '表尾']
复制代码 分页查询结果累积适合用 extend:- def query_users(db_connection, conditions):
- all_results = []
- page = 1
- page_size = 100
- while True:
- start = (page - 1) * page_size + 1
- end = start + page_size
- results = [f'用户{i}' for i in range(start, min(end, start + 1000))]
- if not results:
- break
- all_results.extend(results)
- page += 1
- if page > 3:
- break
- return all_results
- users = query_users(None, {})
- print(f'共查询到 {len(users)} 个用户')
复制代码
七、常见错误与最佳实践
错误一:append 和 extend 混淆。append([4,5]) 添加整个列表,extend([4,5]) 才展开添加两个元素。
错误二:在循环中用 insert 构建列表,会变成 O(n^2)。应先用 append,最后 reverse,或者使用 deque。
错误三:忘记 append 返回 None。nums = [1,2].append(3) 会让 nums 变成 None。
错误四:对元组调用 append。元组不可变,会抛 AttributeError。
错误五:多线程中复合操作不加锁。append 本身受 GIL 保护,但“检查再添加”等复合操作仍需要 lock。
最佳实践:
添加单个元素到末尾:append()
添加多个元素到末尾:extend() 或 +=
在指定位置插入一个元素:insert()
在指定位置插入多个元素:切片赋值
频繁头部插入:collections.deque
从零构建列表:列表推导式
这四种方式都返回 None,因为都是原地修改列表。理解添加位置、添加数量和复杂度后,选型就不容易出错。 |