有什么理由不使用'+'连接两个字符串?

时间:2012-04-06 12:42:34

标签: python string-concatenation anti-patterns

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

但是,我听说它(including here on Stack Overflow)你永远不会使用+进行字符串连接,而是始终使用''.join或格式字符串。我不明白为什么如果你只是连接两个字符串就是这种情况。如果我的理解是正确的,那么它不应该花费二次时间,我认为a + b''.join((a, b))'%s%s' % (a, b)更清晰,更具可读性。

使用+连接两个字符串是一种好习惯吗?或者是否有我不知道的问题?

8 个答案:

答案 0 :(得分:102)

两个字符串与+连接起来没有错。事实上,它比''.join([a, b])更容易阅读。

你是对的,虽然用+连接超过2个字符串是O(n ^ 2)操作(与join的O(n)相比)因此变得效率低下。然而,这与使用循环无关。即使a + b + c + ...是O(n ^ 2),原因是每个连接都会生成一个新字符串。

CPython2.4及更高版本试图减轻这种情况,但是当连接超过2个字符串时仍然建议使用join

答案 1 :(得分:46)

Plus运算符是连接两个 Python字符串的完美解决方案。但是如果你继续添加两个以上的字符串(n> 25),你可能想要考虑别的事情。

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

答案 2 :(得分:7)

假设一个人永远不应该使用+进行字符串连接,而是总是使用'​​'.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)]

让我们使用第一个列表运行timeit

>>> 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. 连接操作否。
  3. 在开发过程中,预成熟优化的过程是邪恶的。

答案 3 :(得分:6)

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

比方说,一个函数需要一个参数,你写它期望获得一个字符串:

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提供的功能。

答案 4 :(得分: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)时间。

至少在性能方面,使用+很好。

答案 5 :(得分:2)

根据Python文档,使用str.join()将为您提供跨Python各种实现的性能一致性。虽然CPython优化了s = s + t的二次行为,但其他Python实现可能不会。

  

CPython实现细节:如果s和t都是字符串,有些是   像CPython这样的Python实现通常可以就地执行   对s = s + t或s + = t形式的赋值进行优化。什么时候   适用时,此优化使二次运行时间更少   有可能。此优化既是版本又是实现   依赖。对于性能敏感的代码,最好使用   str.join()方法,确保一致的线性串联   跨版本和实现的性能。

Sequence Types in Python docs(见脚注[6])

答案 6 :(得分:2)

我在python 3.8中使用以下内容

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

答案 7 :(得分:0)

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

因为Code的编写方式不会影响Python的其他实现(PyPy,Jython,IronPython,Cython,Psyco等)

形成一个+ = b或a = a + b即使在CPython中也很脆弱,并且在不使用 引用 (引用计数是一种将参考,指针或句柄的数量存储到资源(如对象,内存块,磁盘空间或其他资源)

的技术)

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