Python list 有哪些特点?append、extend、insert 有什么区别?
简化版
Python 的 list 是有序、可变、可重复的序列,底层可以理解为动态数组。append 在末尾追加一个元素,extend 把可迭代对象中的多个元素逐个追加进去,insert 在指定位置插入元素,通常会移动后面的元素。
详细版
常见操作:
nums = [1, 2]
nums.append([3, 4])
print(nums) # [1, 2, [3, 4]]
nums = [1, 2]
nums.extend([3, 4])
print(nums) # [1, 2, 3, 4]
nums.insert(1, 99)
print(nums) # [1, 99, 2, 3, 4]
区别:
| 方法 | 含义 | 示例结果 |
|---|---|---|
append(x) | 把 x 作为一个元素追加 | [1, [2, 3]] |
extend(iterable) | 把可迭代对象逐个追加 | [1, 2, 3] |
insert(i, x) | 在下标 i 前插入 x | 需要移动元素 |
还要注意:很多 list 原地修改方法返回 None:
nums = [3, 1, 2]
result = nums.sort()
print(result) # None
print(nums) # [1, 2, 3]
这是为了提醒你:sort() 改的是原列表,不是返回新列表。
完整版教学
一、list 的核心特性
list 是 Python 中最常用的容器之一:
items = ["java", "python", "go"]
它的特点:
- 有序:元素有位置,可以按下标访问。
- 可变:可以增删改。
- 可重复:同一个值可以出现多次。
- 可混合:可以放不同类型对象,但工程上通常不建议混得太乱。
items[0] = "rust"
items.append("python")
list 保存的是对象引用,不是把对象内容“嵌入”进列表。一个列表里可以放 int、str、dict、对象实例,但工程上通常应该保持元素类型一致,否则后续处理和类型推断会很痛苦。面试回答可以说:list 是通用容器,但不是类型设计的借口。
二、为什么说 list 类似动态数组
list 支持按下标快速访问:
items[0]
items[10]
可以把它理解为一段连续的引用数组。列表保存的是对象引用,不是把对象内容全部嵌进去。
当列表容量不够时,解释器会扩容并迁移引用,所以末尾 append 通常很快,但中间插入或删除通常要移动后续元素。
nums.insert(0, 100)
这个操作要把原来的元素整体往后挪,数据量大时成本明显。
可以用数字例子理解:长度为 10000 的列表,在末尾 append 一个元素,通常只写入末尾空位;在下标 0 insert 一个元素,后面约 10000 个引用都要右移。CPython list 会做过度分配,让多次 append 不必每次都扩容,但中间插入和删除仍然绕不开移动成本。
insert(0, X)
插入前: [A, B, C, D]
插入后: [X, A, B, C, D]
A/B/C/D 都要后移
| 操作 | 常见成本直觉 | 原因 |
|---|---|---|
items[i] | O(1) | 直接按下标定位 |
append(x) | 摊还 O(1) | 尾部追加,容量不足才扩容 |
insert(0, x) | O(n) | 后续元素移动 |
pop() | O(1) | 删除末尾 |
pop(0) | O(n) | 后续元素移动 |
三、append、extend、insert 的区别
append 是追加一个对象:
a = [1, 2]
a.append([3, 4])
print(a) # [1, 2, [3, 4]]
extend 是展开可迭代对象:
a = [1, 2]
a.extend([3, 4])
print(a) # [1, 2, 3, 4]
字符串也是可迭代对象:
a = []
a.extend("abc")
print(a) # ['a', 'b', 'c']
如果你想把 "abc" 作为一个整体元素,应该用 append("abc")。
insert(i, x) 是在下标 i 前插入一个对象,不会展开 x。如果 i 小于 0,会按边界处理到开头附近;如果 i 大于长度,会等价于尾部追加。实际开发里,频繁头部插入说明 list 可能不是合适结构,可以考虑 collections.deque。
a = [1, 2]
a.insert(1, [9, 9])
print(a) # [1, [9, 9], 2]
记忆钩子:
append把参数当“一个元素”,extend把参数当“一个可迭代序列”,insert把参数插到指定位置但要移动后面的元素。
四、remove、pop、del 怎么选
删除也有几种方式:
nums = [10, 20, 30, 20]
nums.remove(20) # 删除第一个值为 20 的元素
nums = [10, 20, 30]
x = nums.pop() # 删除并返回最后一个元素
y = nums.pop(0) # 删除并返回下标 0 的元素
nums = [10, 20, 30]
del nums[1]
选择方式:
- 按值删第一个匹配:
remove。 - 删除并拿到元素:
pop。 - 按下标或切片删除,不需要返回:
del。
还要注意异常边界:remove(x) 找不到值会抛 ValueError,pop(i) 下标越界会抛 IndexError,del nums[i] 下标越界也会报错。若只是想安全过滤元素,列表推导式可能更清楚。
nums = [1, 2, 3, 2]
nums = [x for x in nums if x != 2]
print(nums) # [1, 3]
五、原地方法为什么返回 None
这些方法通常返回 None:
list.sort()
list.reverse()
list.append(x)
list.extend(iterable)
这样设计是为了避免误以为它们创建了新列表。
错误写法:
nums = [3, 1, 2]
nums = nums.sort()
print(nums) # None
正确写法:
nums = [3, 1, 2]
nums.sort()
如果需要新列表,用:
new_nums = sorted(nums)
这个约定是 Python 容器 API 的一个信号:会原地修改对象的方法,通常不返回修改后的对象,避免链式调用造成误解。append、extend、sort、reverse 都是这个思路。相对地,sorted(nums)、reversed(nums)、切片等会产生新对象或迭代器,要看具体 API。
六、浅拷贝和共享引用边界
list 的拷贝也经常和方法题一起追问。a.copy()、a[:]、list(a) 通常只复制外层列表,里面的可变对象仍共享。列表保存的是引用,所以“新列表”不等于“里面每个对象都新建”。
a = [[1], [2]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 99], [2]]
这和 append/extend 的区别也有关:append([3]) 会把内部列表作为一个元素保存引用;extend([3]) 才会把元素逐个放进去。面试回答如果能把动态数组、原地方法、浅拷贝串起来,就比只背 API 更扎实。
七、常见误区与追问
- 误区:
append([3, 4])会追加两个元素。 append 把参数作为一个整体元素,结果会多一个嵌套列表。 - 误区:
extend("abc")会追加字符串整体。 字符串可迭代,会逐个字符追加;整体追加要用append("abc")。 - 误区:
nums = nums.sort()能得到排序列表。sort()原地排序并返回None,新列表用sorted(nums)。 - 追问:为什么头部插入比尾部 append 慢? list 类似动态数组,头部插入要移动后续大量引用。
- 追问:
remove和pop区别是什么?remove按值删第一个匹配且不返回,pop按下标删并返回元素。 - 追问:list 拷贝后内部对象还会共享吗? 浅拷贝只复制外层列表,嵌套可变对象仍共享。
八、加强记忆
把 list 记成“有序可变的引用数组”:下标访问快,尾部 append 摊还快,中间插入删除要移动元素。append 加一个整体,extend 展开可迭代对象,insert 插到指定位置;原地修改方法多返回 None,拷贝默认多是浅拷贝。回答这题时用 [1, 2].append([3, 4]) 和 extend([3, 4]) 的对比最直观。