【问题标题】:Do Python list comprehensions append at each iteration?Python 列表推导是否在每次迭代时附加?
【发布时间】:2017-12-03 18:06:44
【问题描述】:

我试图了解 Python 中列表解析的性能,以及使用它们与 for 循环创建列表的权衡。使用 for 循环将元素追加到列表的已知性能成本之一是,在每次迭代时,它是 O(k)(其中 k 是列表的长度),因为追加需要到达列表的末尾添加一个额外的元素。

这对列表推导有什么作用?在每次迭代时,是否需要到达新列表的末尾才能追加一个新元素?

# For loop:
# O(n*k) (k=number of elements currently in list) time complexity:
new_list = []
for i in range(n): # O(n)
  new_list.append(i) # O(k) 

# List comprehension:
new_list = [i for i in range(n)] # Is this O(n)? 

我搜索了 Python 文档、Stack Overflow 和其他网站,但找不到任何相关信息。有许多资源可获取有关列表理解的更多更高级别的信息,但没有这样的具体信息。

如果您无法提供答案,请指导我或告诉我如何查看实际的底层 Python 列表理解代码,以便我自己完成此操作?

【问题讨论】:

  • new_list.append(i) 不是O(k)。插入可能是。但它们被优化为使用append
  • 查看cython源代码?为了提高性能,您可以使用 % timeit 魔术运算符。
  • “列表推导比等效的 for 循环运行得快一点(除非你只是想丢弃结果)。”来自:wiki.python.org/moin/PythonSpeed

标签: python python-3.x list


【解决方案1】:

附加到列表是摊销 O(1) 而不是O(k);列表被实现为可变长度数组,而不是链接列表。复杂性适用于带有my_list.append 调用的for 循环和列表理解(剧透警报, 附加)。

所以在这两种情况下。复杂度为O(N)

列表理解通常表现更好,因为它们专门做一件事: 创建列表。为它们生成的字节码是特定的。 (见LIST_APPEND字节码)

另请注意,列表理解(如 for 循环)不一定在每次迭代时附加。使用if 子句过滤掉您正在循环的可迭代对象的元素是常用的。


如果您想了解列表理解是如何在 CPython 中实现的,您可以查看为它们生成的字节码,并通过ceval.c 扫描每个执行的操作。

编译一个列表理解表达式后,可以用dis看到字节码:

dis(compile('[i for i in range(10)]', '', 'exec').co_consts[0])
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE

然后,scan through the cases in ceval.c 或查看dis module 中的文档。

【讨论】:

  • 可能值得一提的是,为了完整性,您还可以在列表理解中嵌套 for 循环,但答案很好!
猜你喜欢
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-16
  • 2018-03-31
  • 1970-01-01
  • 2020-05-27
相关资源
最近更新 更多