我能够在线找到大量关于在Python中使用+
或+=
进行字符串连接是非常低效和不良做法的信息(在Stack Overflow和其他地方)。
但我似乎找不到+=
为什么如此低效的原因。除了提到这里有一些情况下会有20%的优化(不清楚是哪些情况),我找不到任何额外的信息。
在更深层次上,''.join()
相对于其他Python连接方法的优势是什么?
我能够在线找到大量关于在Python中使用+
或+=
进行字符串连接是非常低效和不良做法的信息(在Stack Overflow和其他地方)。
但我似乎找不到+=
为什么如此低效的原因。除了提到这里有一些情况下会有20%的优化(不清楚是哪些情况),我找不到任何额外的信息。
在更深层次上,''.join()
相对于其他Python连接方法的优势是什么?
假设您有以下代码,从三个字符串构建一个字符串:
x = 'foo'
x += 'bar' # 'foobar'
x += 'baz' # 'foobarbaz'
'foobar'
,然后才能分配和创建 'foobarbaz'
。+=
,字符串的整个内容以及要添加到其中的任何内容都需要复制到一个全新的内存缓冲区中。换句话说,如果您有 N
个字符串要连接,您需要分配大约 N
个临时字符串,并且第一个子字符串会被复制 ~N 次。最后一个子字符串只会被复制一次,但平均每个子字符串会被复制 ~N/2
次。.join
,Python 可以玩一些技巧,因为不需要创建中间字符串。CPython 事先确定它需要多少内存,然后分配一个正确大小的缓冲区。最后,它将每个部分复制到新缓冲区中,这意味着每个部分只复制一次。
还有其他可行的方法可能会在某些情况下带来更好的+=
性能。例如,如果内部字符串表示实际上是一个rope
或者运行时实际上足够聪明以某种方式找出临时字符串对程序没有用处并将其优化掉。
然而,CPython确实不可靠地执行这些优化(尽管它在一些边缘案例中可能会这样做),由于它是最常用的实现,许多最佳实践都是基于对CPython的良好适应。拥有一个标准化的规范集也使得其他实现更容易集中他们的优化努力。
foo += bar + baz
这样的代码。如果您展示会导致分配的代码,那么您的答案可能更有意义。 - Bryan Oakleyfoo += bar; foo += baz;
的行为与这篇文章描述的完全相同。foo = foo + bar + baz;
也一样。foo += bar + baz
的行为略有不同,但速度并没有更快。 - Mooing Duckfoo += bar
这个表达式。初学者可能会被这个答案绊倒,并想知道为什么Python在没有表达式的情况下分配了空间给“foobar”。 - Bryan Oakleysum([x for x in ...])
这样的东西通过时(为什么浪费内存?)... - mgilsons = ""
for l in some_list:
s += l
l
占用20字节,而s
已经被解析为50 KB大小。当Python连接s + l
时,它会创建一个新的字符串,其中包含50,020个字节,并将50 KB从s
复制到这个新字符串中。也就是说,对于每个新行,程序都会移动50 KB的内存,并且不断增加。在读取100个新行(仅2 KB)后,代码片段已经移动了超过5 MB的内存。更糟糕的是,在赋值之后。s += l
+=
,并且左边的名称绑定到一个只有一个引用的字符串,它会尝试原地调整该字符串的缓冲区大小(根据一些底层内存分配细节,这可能起作用或者不起作用)。当它起作用时,它可以使重复的+=
操作更快(实际上,使用带有+=
的循环可能比使用"".join
更快)。不使用它的主要原因是为了跨解释器兼容性。 - Blckknght
+=
在处理字符串和整数时表现不同。Python 可能需要更长时间来确定+=
操作的数据类型,如整数则为加法,如果是字符串则为连接。而在' '.join()
操作中,它仅期望字符串元素 - 这使得 Python 不必担心处理的数据类型。 - stuartnox+=
的潜在性能成本时,可以阅读 Shlemiel the painter的故事(最初在这里讲述:http://www.joelonsoftware.com/articles/fog0000000319.html)。尽管 Python 中+=
的确切原因可能与 C 中的strcat
的O(N)
复杂度不完全相同,但两者有些相似之处。 - Blckknght