为什么Python中使用星号创建元组数组的速度远高于for循环?
两种元组生成写法的性能差异原因
核心差异底层逻辑
两种写法性能差出数十倍的核心原因是执行层级和对象创建开销的巨大差距:
1. 高性能写法((0,) * x,) * y的执行逻辑
这个写法的所有核心操作都是在CPython的C语言层面完成,有极高的优化度:
- 首先执行
(0,) * x:元组是不可变对象,CPython对*重复操作做了专门优化,直接一次性申请可以容纳x个元素的内存,直接填充0的引用,完全不需要走Python层面的循环。 - 再执行
(上一步生成的元组,) * y:同样是C层面的重复操作,而且因为元组不可变,CPython不会创建y个新的内层元组,仅会把同一个内层元组的引用复制y次放入外层元组,几乎没有额外开销。 - 整个过程仅创建2个元组对象,没有任何Python层面的循环、变量绑定操作。
2. 低性能写法tuple(tuple(0 for _ in range(x)) for _ in range(y))的执行逻辑
这个写法的核心循环全在Python字节码层面执行,开销极高:
- 两层都用了生成器表达式,每一层的
for _ in range(...)循环都是Python解释器逐条执行字节码完成,每次迭代都要做循环变量_的赋值、range迭代器的步进操作,执行效率远低于C层面的操作。 - 内层每次循环都要新生成一个生成器对象,再将生成器遍历转为元组,这个过程要执行x次0的生成操作,y次内层元组的创建、内存申请操作。
- 整个过程要创建y个内层元组、2y个生成器对象,还要执行x*y次Python层面的元素生成操作,开销随x、y增长线性放大,这也是测试中规模越大性能差距越明显的原因。
额外注意事项
((0,) * x,) * y的实现依赖元组不可变的特性,所有内层元组都是同一个对象的引用。如果你后续需要修改内部元素,或者要把填充值换成列表、字典这类可变对象,这个写法会出现修改一个所有子元素同步变动的副作用,但如果仅存储不可变类型(比如数字、字符串、元组),这个写法的性能优势非常明显。
内容的提问来源于stack exchange,提问作者Ariadne Paradis
相关产品推荐
相关产品推荐

