我看过很多帖子(例如here和here)谈到Python中的连接,如何做到最好('+'vs','),这更快等等。但我似乎无法找出为什么这很重要。 Roger Pate在第一个例子中提到了关于传递多个参数与一个参数的问题,但我仍然不清楚。
那么,为什么连接很重要?什么是一个关键的用例?
答案 0 :(得分:6)
因为通常+
n
字符串会导致n-1
内存分配O(n)
。通过邻接连接在解析器中完成,并执行1次分配。例如,''.join(iter(s))
连接将执行O(log(n))
总共2n
个内存的分配/副本。
> a = ['a'] * 100000
> def concat(strings):
c = ''
for s in strings:
c += s
return c
> %timeit ''.join(a) # precalculates necessary buffer size
1000 loops, best of 3: 1.07 ms per loop
> %timeit ''.join(iter(a)) # allocates exponentially larger buffers
1000 loops, best of 3: 1.94 ms per loop
> %timeit concat(a) # allocates a new buffer n-1 times
100 loops, best of 3: 7.15 ms per loop
答案 1 :(得分:1)
字符串是Python中的不可变对象,因此您无法修改现有字符串。这意味着字符串的每个串联都会导致创建一个新的字符串对象,并抛弃两个(源对象)。内存分配很昂贵,足以解决这个问题。
因此,当您知道需要连接多个字符串时,请将它们存储在列表中。最后,只需一次,使用''.join(list_of_strings)
加入该列表。这样,新的字符串只会被创建一次。
请注意,这也适用于其他语言。例如,Java和C#都具有StringBuilder
类型,它们基本相同。只要你继续添加新的字符串部分,它们就会在内部将它添加到字符串中,并且只有当你将构建器转换为真正的字符串时,连接才会发生 - 而且只会发生一次。
另请注意,当您在一行中追加几个字符串时,就会发生这种内存分配开销。例如,a + b + c + d
将创建三个中间字符串。如果查看该表达式的字节代码,可以看到:
>>> dis.dis('a + b + c + d')
1 0 LOAD_NAME 0 (a)
3 LOAD_NAME 1 (b)
6 BINARY_ADD
7 LOAD_NAME 2 (c)
10 BINARY_ADD
11 LOAD_NAME 3 (d)
14 BINARY_ADD
15 RETURN_VALUE
每个BINARY_ADD
汇总前两个值,并为堆栈上的结果创建一个新对象。请注意,对于常量字符串文字,编译器足够聪明,可以注意到您正在添加常量:
>>> dis.dis('"foo" + "bar" + "baz"')
1 0 LOAD_CONST 4 ('foobarbaz')
3 RETURN_VALUE
如果你确实在其中有一些可变部分 - 例如,如果你想生成一个格式良好的输出 - 那么你又回到创建中间字符串对象。在这种情况下,使用str.format
是一个好主意,例如'foo {} bar {} baz'.format(a, b)
。