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


123

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

但是,我听说它说过(包括Stack Overflow上的内容),您永远都不要将它+用于字符串连接,而应该始终使用''.join或格式字符串。我不明白为什么只连接两个字符串会出现这种情况。如果我的理解是正确的,则不应该花费二次时间,而且我认为a + b''.join((a, b))或更加简洁易读'%s%s' % (a, b)

+串联两个字符串是否是一种好习惯?还是有我不知道的问题?


它更加整洁,您可以更好地控制不进行串联。但它的速度稍慢一些,但需要权衡:P
Jakob Bowyer'4

+是说快还是慢?又为什么呢
Taymon'4

1
+更快, In [2]: %timeit "a"*80 + "b"*80 1000000 loops, best of 3: 356 ns per loop In [3]: %timeit "%s%s" % ("a"*80, "b"*80) 1000000 loops, best of 3: 907 ns per loop
Jakob Bowyer

4
In [3]: %timeit "%s%s" % (a, b) 1000000 loops, best of 3: 590 ns per loop In [4]: %timeit a + b 10000000 loops, best of 3: 147 ns per loop
Jakob Bowyer'4

1
@JakobBowyer和其他人:“字符串连接不好”参数与速度几乎没有关系,但是利用的自动类型转换__str__。请参阅我的答案以获取示例。
Izkata 2012年

Answers:


119

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

您是对的,尽管用2个以上的字符串进行连接+是O(n ^ 2)操作(与相比,O(n)join)因此效率低下。但是,这与使用循环无关。偶数a + b + c + ...为O(n ^ 2),原因是每个串联产生一个新的字符串。

CPython2.4及更高版本试图缓解这种情况,但是join在连接两个以上的字符串时仍然建议使用。


5
@Mutant:.join需要迭代,因此.join([a,b]).join((a,b))都有效。
弃儿

1
有趣的时机提示即使对于CPython 2.3+,也要在stackoverflow.com/a/12171382/378826(来自Lennart Regebro)上使用++=接受可接受的答案(从2013年开始),并且即使该清除器暴露了解决问题的想法。
Dilettant

49

加号运算符是连接两个 Python字符串的完美解决方案。但是,如果您继续添加两个以上的字符串(n> 25),则可能需要考虑其他问题。

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


2
元组会比列表更好吗?
ThiefMaster 2012年

7
元组会更快-代码只是一个示例:)通常,长字符串输入是动态的。
Mikko Ohtamaa '04年

5
@martineau我认为他的意思是动态生成append()字符串并将其添加到列表中。
彼得C

5
这里需要说:元组通常是SLOWER结构,尤其是在增长时。使用list时,可以使用list.extend(list_of_items)和list.append(item)进行动态串联时,速度更快。
Antti Haapala 2012年

6
为+1 n > 25。人类需要参考点才能从某个地方开始。
n611x007 2013年

8

假设永远不要使用+进行字符串连接,而始终使用''.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会导致不必要的函数调用,这可能会增加开销。

现在回到问题。在所有情况下都应该不鼓励使用+over join吗?

我相信不,应该考虑

  1. 所讨论字符串的长度
  2. 串联操作数。

在开发中过早地进行优化是不明智的。


7
当然,这种想法不是join在循环本身内部使用-而是循环会生成将传递给加入的序列。
jsbueno 2012年

7

与多个人一起工作时,有时很难确切知道正在发生什么。使用格式字符串而不是连接可以避免对我们造成无数次特定烦恼:

说,一个函数需要一个参数,然后编写它以获取字符串:

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


1
+1表示合理的反对意见。我仍然认为我赞成+
Taymon

1
为什么不将foo方法定义为:print'bar:'+ str(zeta)?
EngineerWithJava54321

@ EngineerWithJava54321举一个例子,zeta = u"a\xac\u1234\u20ac\U00008000"-因此,您必须使用print 'bar: ' + unicode(zeta)它来确保不会出错。 %s无需考虑就可以正确执行,而且时间要短得多
Izkata

@ EngineerWithJava54321其他示例在这里不太相关,但是例如,"bar: %s"可能会翻译成"zrb: %s br"其他某种语言。该%s版本将可以使用,但是字符串连接的版本将变得混乱
不堪,

如果他们不知道foo的实现是什么,那么他们将使用any遇到此错误def
Insidesin

3

我做了一个快速测试:

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)时间。

因此,至少在性能方面,使用+还不错。


3
您可以在这里与“ join”案例进行比较。还有其他Python实现的问题,例如pypy,jython,ironpython等...
jsbueno 2012年

3

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

CPython实现细节:如果s和t都是字符串,则某些Python实现(例如CPython)通常可以对s = s + t或s + = t形式的赋值执行就地优化。如果适用,此优化将使二次运行的可能性大大降低。此优化取决于版本和实现。对于性能敏感的代码,最好使用str.join()方法,以确保各个版本和实现之间一致的线性串联性能。

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



0

''.join([a,b])+更好。

因为应该以不损害Python其他实现(PyPy,Jython,IronPython,Cython,Psyco等)的方式编写代码

形式a + = b或a = a + b即使在CPython中也很脆弱,并且在不使用 引用计数的 实现中根本不存在(引用计数是一种存储引用,指针或对a的句柄的技术资源,例如对象,内存块,磁盘空间或其他资源

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


1
a += b适用于Python的所有实现,只是其中一些实现在循环内需要二次时间;问题是关于循环的字符串连接。
Taymon'5
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.