【问题标题】:Python: understanding iterators and `join()` betterPython:更好地理解迭代器和 `join()`
【发布时间】:2015-12-04 09:33:17
【问题描述】:

join() 函数接受一个可迭代对象作为参数。但是,我想知道为什么要:

text = 'asdfqwer'

这个:

''.join([c for c in text])

明显快于:

''.join(c for c in text)

长字符串(即text * 10000000)也是如此。

观察两个长字符串执行的内存占用情况,我认为它们都在内存中创建了一个且只有一个字符列表,然后将它们连接成一个字符串。所以我猜想也许区别仅在于join() 如何从生成器中创建这个列表,以及Python 解释器在看到[c for c in text] 时如何做同样的事情。但是,我只是在猜测,所以我希望有人确认/否认我的猜测。

【问题讨论】:

  • @AvinashRaj:你在哪里看到元组?
  • @Matthias ya,被错误地称为元组而不是生成器。
  • 有趣的是,列表在我的系统上也比直接遍历字符串更快:''.join(text)
  • 亲眼看看:python -m timeit 'text = "asdfqwer" * 10000000; "".join([c for c in text])'(也没有[])。我用列表得到2.04 sec per loop,用生成器得到2.9 sec per loop

标签: python python-internals


【解决方案1】:

join 方法读取其输入两次;一次确定为结果字符串对象分配多少内存,然后再次执行实际连接。传递列表比传递生成器对象要快,生成器对象需要对其进行复制以便迭代两次。

列表推导式不仅仅是包装在列表中的生成器对象,因此在外部构造列表比让join 从生成器对象创建列表要快。生成器对象针对内存效率而非速度进行了优化。

当然,字符串已经是一个可迭代的对象,所以你可以写''.join(text)。 (同样,这并不像从字符串显式创建列表那么快。)

【讨论】:

  • @DanielDarabos “它需要复制” - 通过将其消耗到列表中!
  • 复制我的问题评论,因为您在回答中提到了它:有趣的是,我的系统上的 timeit 表明列表也比此处的直接字符串迭代更快。
  • @Two-BitAlchemist 这是因为PySequence_Fast 只处理特殊情况的列表和元组 - 其他所有内容(包括字符串)都会额外处理一次。
  • 生成器对象很慢。它们针对内存使用进行了优化,而不是迭代速度。从现有生成器创建列表比使用列表推导式构建列表需要更多时间。 (尽管语法相似,列表推导式不仅仅是包装在列表中的生成器对象。)
  • @Andrey 这不是一个很好的测试,因为您使用的是短期循环功能,而我们正在谈论一个必须重新处理生成器(因此必须制作副本)的功能。此外,在您的测试中,生成器仍然稍慢(尽管不明显)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-21
  • 2015-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-09
  • 2018-11-04
相关资源
最近更新 更多