为什么不用’+’连接两个字符串?

Python 中常见的反模式是在循环中使用 +连接字符串序列。这很糟糕,因为 Python 解释器必须为每次迭代创建一个新的字符串对象,并且最终要花费二次方的时间。(在某些情况下,CPython 的最新版本显然可以对此进行优化,但其他实现不能,因此不鼓励程序员依赖这种方法。)''.join是这样做的正确方法。

但是,我听说过(包括 Stack Overflow) ,永远不会应该使用 +进行字符串串联,而不是总是使用 ''.join或格式化字符串。我不明白如果你只连接两个字符串为什么会这样。如果我的理解是正确的,它不应该采取二次时间,我认为 a + b是更干净和更易读的 ''.join((a, b))'%s%s' % (a, b)

使用 +连接两个字符串是好的做法吗? 还是有我没有意识到的问题?

203829 次浏览

Plus 运算符是连接 Python 字符串的完美解决方案。但是,如果您一直添加两个以上的字符串(n > 25) ,您可能需要考虑其他事情。

''.join([a, b, c])技巧是一种性能优化。

字符串与 +连接起来并没有什么错误。实际上它比 ''.join([a, b])更容易读取。

您是正确的,虽然连接超过2个字符串与 +是一个 O (n ^ 2)操作(与 join的 O (n)相比) ,因此变得效率低下。但是,这与使用循环无关。甚至 a + b + c + ...也是 O (n ^ 2) ,原因是每个串联产生一个新的字符串。

CPython2.4及以上版本试图减轻这种影响,但是在连接超过2个字符串时仍然建议使用 join

假设永远不应该使用 + 进行字符串连接,而应该总是使用“。加入也许只是个传说。使用 +的确会创建不必要的不可变字符串对象的临时副本,但另一个不常被引用的事实是,在循环中调用 join通常会增加 function call的开销。让我们以你为例。

创建两个列表,一个从链接 SO 问题和另一个更大的捏造

>>> myl1 = ['A','B','C','D','E','F']
>>> myl2=[chr(random.randint(65,90)) for i in range(0,10000)]

让我们创建两个函数,UseJoinUsePlus,以使用各自的 join+功能。

>>> def UsePlus():
return [myl[i] + myl[i + 1] for i in range(0,len(myl), 2)]


>>> def UseJoin():
[''.join((myl[i],myl[i + 1])) for i in range(0,len(myl), 2)]

让我们用第一个列表来计时

>>> myl=myl1
>>> t1=timeit.Timer("UsePlus()","from __main__ import UsePlus")
>>> t2=timeit.Timer("UseJoin()","from __main__ import UseJoin")
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000)
2.48 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
2.61 usec/pass
>>>

它们具有几乎相同的运行时。

让我们使用 cProfile

>>> myl=myl2
>>> cProfile.run("UsePlus()")
5 function calls in 0.001 CPU seconds


Ordered by: standard name


ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1    0.001    0.001    0.001    0.001 <pyshell#1376>:1(UsePlus)
1    0.000    0.000    0.001    0.001 <string>:1(<module>)
1    0.000    0.000    0.000    0.000 {len}
1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
1    0.000    0.000    0.000    0.000 {range}




>>> cProfile.run("UseJoin()")
5005 function calls in 0.029 CPU seconds


Ordered by: standard name


ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1    0.015    0.015    0.029    0.029 <pyshell#1388>:1(UseJoin)
1    0.000    0.000    0.029    0.029 <string>:1(<module>)
1    0.000    0.000    0.000    0.000 {len}
1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
5000    0.014    0.000    0.014    0.000 {method 'join' of 'str' objects}
1    0.000    0.000    0.000    0.000 {range}

而且看起来使用 Join 会导致不必要的函数调用,这可能会增加开销。

现在回到问题上来。在所有情况下,是否应该劝阻使用 +而不是 join

我认为不,事情应该被考虑在内

  1. 问题中字符串的长度
  2. 连接操作编号。

而偏离发展轨道的过早优化是有害的。

我做了一个快速测试:

import sys


str = e = "a xxxxxxxxxx very xxxxxxxxxx long xxxxxxxxxx string xxxxxxxxxx\n"


for i in range(int(sys.argv[1])):
str = str + e

并计时:

mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  8000000
8000000 times


real    0m2.165s
user    0m1.620s
sys     0m0.540s
mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  16000000
16000000 times


real    0m4.360s
user    0m3.480s
sys     0m0.870s

显然,对于 a = a + b情况有一个优化,它没有像人们可能怀疑的那样显示 O (n ^ 2)时间。

因此,至少在性能方面,使用 +是可行的。

当与多个人一起工作时,有时很难确切地知道发生了什么。使用格式字符串而不是连接可以避免一个特别的烦恼,这个烦恼已经在我们身上发生了很多次了:

比方说,一个函数需要一个参数,你写它的时候希望得到一个字符串:

In [1]: def foo(zeta):
...:     print 'bar: ' + zeta


In [2]: foo('bang')
bar: bang

因此,这个函数可以在整个代码中经常使用。您的同事可能确切地知道它做什么,但不一定完全了解内部的最新情况,并且可能不知道函数需要一个字符串。因此,他们可能会得到这样的结果:

In [3]: foo(23)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)


/home/izkata/<ipython console> in <module>()


/home/izkata/<ipython console> in foo(zeta)


TypeError: cannot concatenate 'str' and 'int' objects

如果只使用格式化字符串,就不会有任何问题:

In [1]: def foo(zeta):
...:     print 'bar: %s' % zeta
...:
...:


In [2]: foo('bang')
bar: bang


In [3]: foo(23)
bar: 23

对于定义 __str__的所有类型的对象也是如此,它也可以传入:

In [1]: from datetime import date


In [2]: zeta = date(2012, 4, 15)


In [3]: print 'bar: ' + zeta
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)


/home/izkata/<ipython console> in <module>()


TypeError: cannot concatenate 'str' and 'datetime.date' objects


In [4]: print 'bar: %s' % zeta
bar: 2012-04-15

所以,是的: 如果您可以使用格式化字符串 动手吧,并利用 Python 提供的优势。

”. join ([ a,b ]) 是比 +更好的解决方案。

因为编写代码的方式不应该使其他 Python 实现(PyPy、 Jython、 IronPython、 Cython、 Psyco 等等)处于劣势

Form a + = b 或 a = a + b 即使在 CPython 中也是脆弱的,在实现 没有用的东西 重新计算 (引用计数是一种存储对象、内存块、磁盘空间或其他资源等资源的引用、指针或句柄的数量的技术中根本不存在)

Https://www.python.org/dev/peps/pep-0008/#programming-recommendations

根据 Python 文档,使用 str.join ()将使您在不同的 Python 实现之间获得性能一致性。尽管 CPython 优化了 s = s + t 的二次行为,但其他 Python 实现可能不会。

CPython 实现细节 : 如果 s 和 t 都是字符串,则 诸如 CPython 之类的 Python 实现通常可以就地执行 形式 s = s + t 或 s + = t 的赋值优化 适用,这种优化使二次运行时间大大减少 这个优化既是版本也是实现 对于性能敏感的代码,最好使用 Join ()方法,确保一致的线性连接 跨版本和实现的性能。

Python 文档中的序列类型 (参见脚注[6])

我在 python 3.8中使用了以下内容

string4 = f'{string1}{string2}{string3}'