You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中list_a + [value]为何比[*list_a, value]性能更优?

为什么[*list_a, value]比list_a + [value]在Python 3.12.3中性能更差?

我原本以为list_b = [*list_a, value]生成带新增元素的新列表时,性能会比list_b = list_a + [value]更优——毕竟后者需要生成中间列表[value]。但在Python 3.12.3/Windows 10环境下的基准测试结果却完全相反:

性能对比图

测试代码

from timeit import timeit
import random
import matplotlib.pyplot as plt

num_data_points = 1000
step = 10
methods = [
    # ordered from slowest to fastest to make the key easier to read
    # "list_b = list_a.copy(); list_b += (value,)"
    # "list_b = list_a.copy(); list_b.append(value)"
    # "list_b = list(list_a); list_b.append(value)"
    "list_b = [*list_a, value]",
    "list_b = list_a + [value]",
]

x = list(range(0, num_data_points * step, step))
y = [[] for _ in methods]
for i in x:
    list_a = list(range(i))
    random.shuffle(list_a)
    value = random.randint(0, num_data_points * step)
    setup = f"list_a = {list_a}; value = {value}"
    for method_index, method in enumerate(methods):
        y[method_index].append(timeit(method, setup=setup, number=800))
    print(i, "out of", num_data_points * step)

ax = plt.axes()
for method_index, method in enumerate(methods):
    ax.plot(x, y[method_index], label=method)
ax.set(xlabel="size of the list", ylabel="time (s) (lower is better)")
ax.legend()
ax.figure.canvas.get_default_filetype = lambda: 'svg'
plt.show()

反汇编分析

我通过反汇编对比了两种方法的字节码:

反汇编代码

import dis

list_a = [1, 2, 3]
value = 4

def method_1():
    list_b = [*list_a, value]

def method_2():
    list_b = list_a + [value]

if __name__ == '__main__':
    dis.dis(method_1)
    dis.dis(method_2)

字节码结果

# list_b = [*list_a, value]
  6           0 RESUME                   0

  7           2 BUILD_LIST               0
              4 LOAD_GLOBAL              0 (list_a)
             14 LIST_EXTEND              1
             16 LOAD_GLOBAL              2 (value)
             26 LIST_APPEND              1
             28 STORE_FAST               0 (list_b)
             30 RETURN_CONST             0 (None)

# list_b = list_a + [value]
  9           0 RESUME                   0

 10           2 LOAD_GLOBAL              0 (list_a)
             12 LOAD_GLOBAL              2 (value)
             22 BUILD_LIST               1
             24 BINARY_OP                0 (+)
             28 STORE_FAST               0 (list_b)
             30 RETURN_CONST             0 (None)

性能差异的原因

从字节码和CPython底层实现来看,核心差异在于内存分配策略和操作效率:

  1. [*list_a, value]的操作路径:

    • 先创建一个空列表(BUILD_LIST 0)
    • 通过LIST_EXTEND将list_a的所有元素添加到空列表中:这一步需要先获取list_a的长度,扩容空列表到对应大小,再复制元素
    • 最后通过LIST_APPEND添加单个元素:如果当前列表的容量刚好等于len(list_a),这一步会触发额外的内存扩容操作
  2. list_a + [value]的操作路径:

    • 先创建长度为1的中间列表[value](BUILD_LIST 1),这个操作的开销极小
    • 调用列表的__add__方法(BINARY_OP +):该方法会直接计算最终列表的总长度(len(list_a) + 1),一次性分配足够的内存,然后批量复制list_a和[value]的元素,全程不需要额外扩容

另外,CPython对列表的__add__操作做了高度优化——针对两个列表的拼接,它会直接利用内存复制的方式完成,避免了逐个元素添加的开销;而LIST_EXTEND是通用的可迭代对象扩展逻辑,即使操作对象是列表,也会有额外的类型检查和迭代处理步骤,效率不如专用的__add__。

综上,虽然[*list_a, value]看似避免了中间列表的创建,但实际操作带来的内存扩容和通用迭代开销,远大于创建一个长度为1的小列表的成本,最终导致后者性能更优。

内容的提问来源于stack exchange,提问作者Danny Lin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 06:12:34