首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python的列表操作把我坑惨了,原来append和extend的区别这么大

Python的列表操作把我坑惨了,原来append和extend的区别这么大

原创
作者头像
风一样的男子
发布于 2026-09-30 13:13:12
发布于 2026-09-30 13:13:12
940
举报
文章被收录于专栏:编程教程编程教程

小陈那天差点把键盘摔了。

他写了一个数据处理脚本,需要把多个来源的数据合并到一个大列表里。逻辑很简单:先创建一个空列表all_data,然后遍历每个数据源,把里面的每条记录加进去。代码大概长这样:

代码语言:javascript
复制
all_data = []
source1 = [1, 2, 3]
source2 = [4, 5, 6]

all_data.append(source1)
all_data.append(source2)
print(all_data)

他以为会得到[1, 2, 3, 4, 5, 6]。结果终端打印出来的是:

代码语言:javascript
复制
[[1, 2, 3], [4, 5, 6]]

列表里套了两个列表,完全不是他想要的扁平结构。他盯着屏幕愣了几秒,然后改成extend,问题瞬间解决。他叹了口气:“就差了四个字母,结果天差地别。”

这个坑太常见了。append和extend看起来都是往列表里加东西,但加的方式完全不同。用错了,数据结构就会悄悄变形,而且往往不会报错,直到下游代码崩溃你才发现。

先看一个最直观的对比

我们把两个方法放在一起跑一遍:

代码语言:javascript
复制
lst1 = [1, 2, 3]
lst2 = [4, 5, 6]

lst1.append(lst2)
print("append 之后:", lst1)

lst3 = [1, 2, 3]
lst4 = [4, 5, 6]

lst3.extend(lst4)
print("extend 之后:", lst3)

输出:

代码语言:javascript
复制
append 之后: [1, 2, 3, [4, 5, 6]]
extend 之后: [1, 2, 3, 4, 5, 6]

append把lst2当成一个整体,塞进了lst1的末尾。lst1的长度从3变成4,第四个元素是[4, 5, 6]这个列表对象。

extend把lst4里的每个元素逐个取出来,依次追加到lst3末尾。lst3的长度从3变成6,六个元素都是独立的整数。

用一个生活场景来理解:你有一个水果篮,里面已经装了苹果。现在你手里有一袋橘子。

append的做法是:把整袋橘子直接扔进水果篮。篮子里现在有一个苹果,和一袋橘子。

extend的做法是:把袋子打开,把里面的橘子一个一个拿出来,放进水果篮。篮子里现在有一个苹果,和若干个橘子。

区别一目了然。

append:只加一个东西

append的签名是list.append(x)。它接受任意一个对象x,把它作为单个元素追加到列表末尾。列表长度只增加1。

这个“任意对象”包括:整数、字符串、列表、字典、元组、自定义对象,甚至另一个列表。不管x是什么,append都把它当作一个整体。

代码语言:javascript
复制
lst = []
lst.append(1)           # [1]
lst.append("hello")     # [1, "hello"]
lst.append([2, 3])      # [1, "hello", [2, 3]]
lst.append({"a": 1})    # [1, "hello", [2, 3], {"a": 1}]

每次调用,列表长度加1。元素就是传入的那个对象本身。

extend:拆开可迭代对象,逐个添加

extend的签名是list.extend(iterable)。它接受一个可迭代对象,遍历它,把每个元素依次追加到列表末尾。列表长度增加的是可迭代对象的长度。

代码语言:javascript
复制
lst = []
lst.extend([1, 2, 3])        # [1, 2, 3]
lst.extend("abc")            # [1, 2, 3, 'a', 'b', 'c']
lst.extend((4, 5))           # [1, 2, 3, 'a', 'b', 'c', 4, 5]
lst.extend({"x": 1, "y": 2}) # 添加的是键:['x', 'y']

注意最后一行:字典是可迭代对象,但迭代它得到的是键,不是键值对。所以extend一个字典,会把它的键逐个加进去。如果你想要值,得用dict.values()。

extend要求参数必须是可迭代的。如果传一个整数,会直接报错:

代码语言:javascript
复制
lst = []
lst.extend(5)
代码语言:javascript
复制
TypeError: 'int' object is not iterable

而append不会报错,它接受任何对象。

为什么字符串是重灾区?

字符串是最容易被误用的可迭代对象。

假设你想把一个字符串作为一个整体添加到列表里:

代码语言:javascript
复制
lst = ["hello"]
lst.append("world")
print(lst)  # ['hello', 'world']

没问题。但如果你手滑用了extend:

代码语言:javascript
复制
lst = ["hello"]
lst.extend("world")
print(lst)  # ['hello', 'w', 'o', 'r', 'l', 'd']

字符串被拆成了单个字符。因为字符串是可迭代的,extend会遍历它,把每个字符当作一个元素加进去。

这种错误在处理文件路径、用户名、标签时特别常见。你以为加了一个字符串,结果列表里多了一堆字符。

字典也是坑

字典也是可迭代对象,但迭代的是键。所以:

代码语言:javascript
复制
lst = []
lst.extend({"name": "Alice", "age": 30})
print(lst)  # ['name', 'age']

如果你想要的是字典本身作为一个元素,应该用append:

代码语言:javascript
复制
lst = []
lst.append({"name": "Alice", "age": 30})
print(lst)  # [{'name': 'Alice', 'age': 30}]

如果你想把字典的值加进去,应该用extend(d.values())。

性能差异:extend通常更快

除了语义不同,extend在性能上也通常优于多次append。

因为extend是在C层面实现的,它知道要添加多少个元素,可以一次性调整列表的容量,减少内存重新分配的次数。而循环调用append,每次都可能触发容量检查,虽然Python的列表有动态扩容机制,但反复调用仍有开销。

我们来实测一下:

代码语言:javascript
复制
import time

# 用 append 逐个添加
start = time.time()
lst = []
for i in range(1000000):
    lst.append(i)
print("append 耗时:", time.time() - start)

# 用 extend 一次性添加
start = time.time()
lst = []
lst.extend(range(1000000))
print("extend 耗时:", time.time() - start)

在我的机器上,append循环大约0.08秒,extend大约0.02秒。差距明显。

当然,如果你已经有现成的列表,直接extend比写循环append更简洁也更快。

那 + 和 += 呢?

列表还支持+运算符和+=运算符,它们也能合并列表。

代码语言:javascript
复制
a = [1, 2]
b = [3, 4]
c = a + b       # [1, 2, 3, 4]
a += b          # a 变成 [1, 2, 3, 4]

+会创建一个新列表,原列表不变。+=是原地修改,等价于extend。

注意:+=和extend一样,会把右边的可迭代对象拆开。但+要求两边都是列表,不能直接加字符串或元组。

代码语言:javascript
复制
a = [1, 2]
a += "abc"      # 等价于 extend,得到 [1, 2, 'a', 'b', 'c']
a = a + "abc"   # 报错,列表不能和字符串相加

所以,如果你想把一个字符串拆成字符加进去,用+=或extend;如果你想把字符串作为一个整体,用append。

切片赋值也能实现类似效果

还有一种不常见但很强大的方式:切片赋值。

代码语言:javascript
复制
lst = [1, 2, 3]
lst[len(lst):] = [4, 5, 6]
print(lst)  # [1, 2, 3, 4, 5, 6]

这等价于extend。但可读性不如extend,一般不建议这样写。

一个容易忽略的细节:extend 可以接受任何可迭代对象

extend的参数不一定是列表。它可以是任何可迭代对象:元组、集合、生成器、range、字符串、字典的键视图,甚至是你自己实现的迭代器。

代码语言:javascript
复制
lst = []
lst.extend(range(3))        # [0, 1, 2]
lst.extend({10, 20})        # 顺序不确定,集合是无序的
lst.extend(x for x in [7, 8])  # [..., 7, 8]

这意味着你可以直接把一个生成器传给extend,它会边迭代边添加。这在处理大数据时很有用,不需要先构建一个中间列表。

append 和 extend 的返回值都是 None

这是一个小细节,但容易导致链式调用出错。

代码语言:javascript
复制
lst = [1, 2]
result = lst.append(3)
print(result)  # None

append和extend都是原地修改列表,不返回新列表。所以你不能这样写:

代码语言:javascript
复制
lst = [1, 2].append(3)  # lst 是 None

这是新手常犯的错误。记住:它们修改原列表,返回None。

什么时候用 append,什么时候用 extend?

判断标准很简单:你想加一个东西,还是想加一堆东西?

  • 加一个东西:用append。无论这个东西是整数、字符串、列表还是字典,都作为一个整体。
  • 加一堆东西:用extend。这一堆东西必须是一个可迭代对象,里面的每个元素都会被逐个添加。

如果你有一个列表source,想把它里面的元素合并到target里,用target.extend(source)。

如果你想把source本身作为target的一个元素,用target.append(source)。

回到小陈的代码

小陈原本的代码:

代码语言:javascript
复制
all_data = []
source1 = [1, 2, 3]
source2 = [4, 5, 6]

all_data.append(source1)
all_data.append(source2)

得到的是[[1, 2, 3], [4, 5, 6]]。他想要的是扁平列表,所以应该用extend:

代码语言:javascript
复制
all_data = []
all_data.extend(source1)
all_data.extend(source2)

得到[1, 2, 3, 4, 5, 6]。

如果数据源很多,也可以写成循环:

代码语言:javascript
复制
all_data = []
for source in sources:
    all_data.extend(source)

或者更Pythonic的写法:

代码语言:javascript
复制
all_data = [item for source in sources for item in source]

但extend在性能上通常更好,尤其是数据量大时。

一个更隐蔽的坑:append 添加的是引用

最后提一个和append相关但容易被忽略的问题:append添加的是对象的引用,不是副本。

代码语言:javascript
复制
lst = []
inner = [1, 2]
lst.append(inner)
inner.append(3)
print(lst)  # [[1, 2, 3]]

修改inner之后,lst里的那个元素也跟着变了,因为它们指向同一个列表对象。如果你想要一个独立的副本,需要显式复制:

代码语言:javascript
复制
lst.append(inner.copy())

这个问题在循环中批量append时尤其危险。比如:

代码语言:javascript
复制
lst = []
for i in range(3):
    row = [i] * 3
    lst.append(row)

每次循环都新建了row,所以没问题。但如果你重用同一个列表:

代码语言:javascript
复制
lst = []
row = [0, 0]
for i in range(3):
    row[0] = i
    lst.append(row)
print(lst)

输出是[[2, 0], [2, 0], [2, 0]],因为三个元素都指向同一个row。这是另一个常见的列表陷阱,和append/extend的区别无关,但同样值得警惕。

总结

append和extend的区别,本质上是“加一个”和“加一串”的区别。

append(x):把x作为单个元素追加,列表长度加1。x可以是任何对象。

extend(iterable):遍历iterable,把每个元素逐个追加,列表长度增加len(iterable)。iterable必须是可迭代对象。

用错了,列表结构就会变形。append一个列表会得到嵌套列表,extend一个字符串会得到一堆字符。

记住那个水果篮的比喻:append是整袋扔进去,extend是倒出来一个个放。想清楚你要的是哪种,就不会再被坑了。

小陈后来在代码注释里写了一句话:“合并列表用extend,添加单个用append。别再用append加列表了。” 这个教训,值一下午的调试时间。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 先看一个最直观的对比
  • append:只加一个东西
  • extend:拆开可迭代对象,逐个添加
  • 为什么字符串是重灾区?
  • 字典也是坑
  • 性能差异:extend通常更快
  • 那 + 和 += 呢?
  • 切片赋值也能实现类似效果
  • 一个容易忽略的细节:extend 可以接受任何可迭代对象
  • append 和 extend 的返回值都是 None
  • 什么时候用 append,什么时候用 extend?
  • 回到小陈的代码
  • 一个更隐蔽的坑:append 添加的是引用
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档