加入字符串。生成器或列表理解?

时间:2016-01-16 01:50:34

标签: python

考虑从一个巨大的字符串中提取字母表的问题。

一种方法是

''.join([c for c in hugestring if c.isalpha()])

机制很明确:列表理解生成一个字符列表。 join方法通过访问列表的长度来了解它需要加入多少个字符。

其他方法是

''.join(c for c in hugestring if c.isalpha())

这里生成器理解导致生成器。 join方法不知道要连接多少个字符,因为生成器不具有 len 属性。所以这种连接方式应该比列表理解方法慢。

但是在python中测试表明它并不慢。为什么会这样? 任何人都可以解释加入如何在发电机上工作。

要明确:

sum(j for j in range(100))

不需要知道100,因为它可以跟踪累积总和。它可以使用生成器上的下一个方法访问下一个元素,然后添加到累积和。 但是,由于字符串是不可变的,因此累加连接字符串会在每次迭代中创建一个新字符串。所以这需要很多时间。

3 个答案:

答案 0 :(得分:12)

当你致电str.join(gen),其中gen是一个生成器时,Python会相等于list(gen),然后继续检查结果序列的长度。

具体来说,如果您look at the code implementing str.join in CPython,您会看到此电话:

    fseq = PySequence_Fast(seq, "can only join an iterable");

PySequence_Fast的调用将seq参数转换为列表(如果它不是列表或元组)。

因此,您的两个版本的呼叫几乎完全相同。在列表理解中,您自己构建列表并将其传递到join。在生成器表达式版本中,您传入的生成器对象在list的开头变为join,并且其余代码对两个版本的操作都相同。

答案 1 :(得分:1)

join()不需要实现为序列元素的顺序附加到更长和更长的累积字符串(对于长序列来说确实非常慢);它只需要产生相同的结果。所以join()可能只是将字符附加到某个内部内存缓冲区,并在最后创建一个字符串。另一方面,列表推导构造需要首先构建列表(通过遍历hugestring的生成器),然后让join()开始工作。

另外,我怀疑join()查看列表的长度,因为它不知道每个元素都是单个字符(在大多数情况下,它不会) - 它可能只是获得一个生成器从列表中。

答案 2 :(得分:1)

至少在我的机器上,对于我测试的情况,列表理解速度更快,可能是由于''.join能够优化内存分配。它可能仅取决于您正在测试的具体示例(例如,如果您正在测试的条件发生频率较低,则CPython提前支付的不知道长度的价格可能会更小):

In [18]: s = ''.join(np.random.choice(list(string.printable), 1000000))

In [19]: %timeit ''.join(c for c in s if c.isalpha())
10 loops, best of 3: 69.1 ms per loop

In [20]: %timeit ''.join([c for c in s if c.isalpha()])
10 loops, best of 3: 61.8 ms per loop