为什么 itertools.chain 比扁平列表理解更快?

2024-04-28

在评论中的讨论中这个问题 https://stackoverflow.com/questions/49630581/why-does-python-forbid-the-use-of-sum-with-strings有人提到,虽然连接字符串序列只需要''.join([str1, str2, ...]),连接一系列列表就像list(itertools.chain(lst1, lst2, ...)),尽管您也可以使用列表理解,例如[x for y in [lst1, lst2, ...] for x in y]。令我惊讶的是,第一种方法始终比第二种方法快:

import random
import itertools

random.seed(100)
lsts = [[1] * random.randint(100, 1000) for i in range(1000)]

%timeit [x for y in lsts for x in y]
# 39.3 ms ± 436 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit list(itertools.chain.from_iterable(lsts))
# 30.6 ms ± 866 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit list(x for y in lsts for x in y)  # Proposed in comments
# 62.5 ms ± 504 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# Loop-based methods proposed in the comments
%%timeit
a = []
for lst in lsts: a += lst
# 26.4 ms ± 634 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
a = []
for lst in lsts: a.extend(lst)
# 26.7 ms ± 728 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

虽然不是一个数量级的差异,但也不容忽视。我想知道情况如何,因为列表理解通常是解决给定问题的最快方法之一。起初我以为也许itertools.chain对象会有一个len认为list构造函数可以用来预分配必要的内存,但事实并非如此(无法调用len on itertools.chain对象)。是一些定制的itertools.chain-to-list转换以某种方式发生或正在发生itertools.chain利用其他机制?

如果相关的话,已在 Windows 10 x64 上的 Python 3.6.3 中进行测试。

EDIT:

毕竟调用似乎是最快的方法.extend每个列表都有一个空列表,如建议的@zwer https://stackoverflow.com/users/7553525/zwer,可能是因为它适用于数据“块”,而不是基于每个元素。


Here is itertools.chain.from_iterable https://github.com/python/cpython/blob/aa0735f597b072c0eb00404c4d7df359ddc26755/Modules/itertoolsmodule.c#L1854。即使您不懂 C,它也不难阅读,并且您可以知道一切都发生在 C 级别(在用于在代码中生成列表之前)。

列表推导式的字节码如下所示:

def f(lsts):
    return [x for y in lsts for x in y]

dis.dis(f.__code__.co_consts[1])
  2           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                18 (to 24)
              6 STORE_FAST               1 (y)
              8 LOAD_FAST                1 (y)
             10 GET_ITER
        >>   12 FOR_ITER                 8 (to 22)
             14 STORE_FAST               2 (x)
             16 LOAD_FAST                2 (x)
             18 LIST_APPEND              3
             20 JUMP_ABSOLUTE           12
        >>   22 JUMP_ABSOLUTE            4
        >>   24 RETURN_VALUE

这些是创建列表理解所涉及的所有 Python 解释器操作。只需将所有操作都放在 C 级别(在chain)而不是让解释器逐步执行每个字节代码步骤(在理解中),这将为您带来性能提升。

不过,这种提升很小,我不会担心。这是Python,可读性高于速度。


Edit:

对于列表包装的生成器理解

def g(lists):
    return list(x for y in lsts for x in y)

# the comprehension
dis.dis(g.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                20 (to 24)
              4 STORE_FAST               1 (y)
              6 LOAD_FAST                1 (y)
              8 GET_ITER
        >>   10 FOR_ITER                10 (to 22)
             12 STORE_FAST               2 (x)
             14 LOAD_FAST                2 (x)
             16 YIELD_VALUE
             18 POP_TOP
             20 JUMP_ABSOLUTE           10
        >>   22 JUMP_ABSOLUTE            2
        >>   24 LOAD_CONST               0 (None)
             26 RETURN_VALUE

因此,解释器在运行按列表解包的生成器表达式时需要执行相似数量的步骤,但正如您所期望的那样,Python 级别的开销list打开生成器的包装(与 C 相对)LIST_APPEND指令)是减慢速度的原因。

dis.dis(f)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x000000000FB58B70, file "<ipython-input-33-1d46ced34d66>", line 2>)
              2 LOAD_CONST               2 ('f.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_FAST                0 (lsts)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

dis.dis(g)
  2           0 LOAD_GLOBAL              0 (list)
              2 LOAD_CONST               1 (<code object <genexpr> at 0x000000000FF6F420, file "<ipython-input-40-0334a7cdeb8f>", line 2>)
              4 LOAD_CONST               2 ('g.<locals>.<genexpr>')
              6 MAKE_FUNCTION            0
              8 LOAD_GLOBAL              1 (lsts)
             10 GET_ITER
             12 CALL_FUNCTION            1
             14 CALL_FUNCTION            1
             16 RETURN_VALUE
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 itertools.chain 比扁平列表理解更快? 的相关文章

  • 从 Excel 获取输入并在 python 脚本中使用这些输入

    如何从 excel 获取输入并在 python 中使用这些输入 看一眼xlrd http pypi python org pypi xlrd 这是我发现的学习如何使用它的最佳参考 http www dev explorer com arti
  • 让 Django 提供可下载文件

    我希望网站上的用户能够下载路径被遮挡的文件 因此无法直接下载它们 例如 我希望 URL 是这样的 http example com download f somefile txt 在服务器上 我知道所有可下载的文件都位于该文件夹中 home
  • 熊猫加入具有不同索引级别/日期时间的数据帧?

    嗨 我有两个 DataFrame 如下所示 dineType menuName unique columns date y m d
  • 使用 Python 将列名称与 CSV 文件中的数据对齐

    这是我用来将数据写入 csv 文件的代码 with open temp csv a as fp a csv writer fp delimiter t data faceXpos faceYpos faceHeight faceWidth
  • 在Python中整齐地绘制PMF

    有没有一个库可以帮助我在 python 中整齐地绘制样本的概率质量函数 如下所示 通过matplotlib pyplot的stem模块 matplotlib pyplot stem args kwargs from matplotlib p
  • R.scale() 和 sklearn.preprocessing.scale() 之间的区别

    我目前正在将数据分析从 R 转移到 Python 当在 R 中缩放数据集时 我将使用 R scale 根据我的理解 它将执行以下操作 x mean x sd x 为了替换该函数 我尝试使用 sklearn preprocessing sca
  • 如何使用Peewee查询多个相似的数据库?

    我遇到了使用 Peewee 查询多个数据库的问题 我有 2 个现有的 mysql 数据库 让我们将它们命名为 A 和 B 结构相似 因为它是两个 Bugzilla 数据库 我使用 Pwiz 生成模型 modelsA py 和 modelsB
  • Python 中 eval("input()") 和 eval(input()) 之间的区别

    我正在尝试以下功能 x eval input 输入为 123 x 的类型也是int 它工作正常 In 22 x eval input enter enter 123 In 24 print type x
  • pandas groupby 并转换为 json 列表

    我有一个如下所示的 pandas 数据框 idx f1 f2 f3 1 a a b 2 b a c 3 a b c 87 e e e 我需要将其他列转换为基于索引列的字典列表 所以 最终结果应该是 idx features 1 f1 a f
  • __author__ 的起源是什么?

    使用私有元数据变量的约定在哪里 author 一个模块内部从何而来 This http mail python org pipermail python dev 2001 March 013328 htmlPython 邮件列表线程似乎暗示
  • 如何在 Python 中连接两个列表?

    这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新的答案或互动 如何在 Python 中连接两个列表 Example listone 1 2 3 lis
  • Python-使用元组作为列表索引[重复]

    这个问题在这里已经有答案了 我有一个元组列表 tuples list 1 0 2 3 3 2 2 0 我想访问二维数组的元素a例如 使用其中一些元组 for i in range 3 print a tuples list i 应该输出的值
  • Python:Factory Boy 生成对象创建时指定长度的列表

    我正在尝试使用 Factoryboy 在创建时指定长度的对象中创建一个列表 我可以创建列表 但由于提供的长度 大小的惰性性质 每次尝试创建具有指定长度的列表都会导致问题 这是我到目前为止所拥有的 class FooFactory facto
  • 使用 Celery 通过 Gevent 进行实时、同步的外部 API 查询

    我正在开发一个 Web 应用程序 该应用程序将接收用户的请求 并且必须调用许多外部 API 来编写对该请求的答案 这可以直接从主 Web 线程使用 gevent 之类的东西来扇出请求来完成 或者 我在想 我可以将传入的请求放入队列中 并使用
  • 检查图像中是否有太薄的区域

    我正在尝试验证雕刻机的黑白图像 更多的是剪贴画图像 不是照片 我需要考虑的主要事情之一是区域的大小 或线条的宽度 因为机器无法处理太细的线条 所以我需要找到比给定阈值更细的区域 以此图为例 竖琴的琴弦可能太细而无法雕刻 我正在阅读有关 Ma
  • Hoare Partitioning算法讲解

    根据许多网站给出的伪代码 我写了这个Hoare分区算法 它采用一个数组 根据给定的主元来分区子数组的开始和结束索引 它工作得很好 但是有人可以解释一下逻辑 它是如何做到这一点的吗 这是代码 def hoare arr start end p
  • 内置模块位于哪里?

    我尝试查找列出的所有目录sys path但我找不到任何builtins py文件 那么它在哪里呢 从字面上看 该模块内置于 python 解释器中 gt gt gt import builtins gt gt gt builtins
  • `numpy.diff` 和 `scipy.fftpack.diff` 在微分时给出不同的结果

    我正在尝试计算一些数据的导数 并且正在尝试比较有限差分的输出和谱方法的输出 但结果却截然不同 我无法弄清楚到底为什么 考虑下面的示例代码 import numpy as np from scipy import fftpack as sp
  • 类unix系统中的python和python3命令有什么区别?

    我通读了每个命令的描述 但每个命令的描述都是完全相同的 所以我不明白这两个命令在类 Unix 系统中的工作方式有何不同 谁能解释其中的区别吗 Python3命令的引入是因为python命令指向了python2 从那时起 Python3 已成
  • 重写 __cmp__ python 函数

    嗨 我是压倒一切的 cmp 如果传递的第二个对象是None 或者如果它不是一个实例someClass 然后返回 1 我不明白这里到底发生了什么 class someClass def cmp self obj if obj None ret

随机推荐