135

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

但是,我听说过(包括 Stack Overflow 上的此处)您永远不应该使用+字符串连接,而是始终使用''.join或格式字符串。如果您只连接两个字符串,我不明白为什么会出现这种情况。如果我的理解是正确的,它不应该花费二次时间,而且我认为a + b它比''.join((a, b))or更干净,更具可读性'%s%s' % (a, b)

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

4

8 回答 8

130

用 . 连接两个字符串并没有错+。确实比阅读更容易''.join([a, b])

你是对的,尽管连接两个以上的字符串+是一个 O(n^2) 操作(与 O(n) for 相比join),因此效率低下。然而,这与使用循环无关。甚至a + b + c + ...是 O(n^2),原因是每个连接都会产生一个新字符串。

CPython2.4 及更高版本试图缓解这种情况,但join在连接超过 2 个字符串时仍然建议使用它。

于 2012-04-06T13:11:17.533 回答
51

Plus operator is perfectly fine solution to concatenate two Python strings. But if you keep adding more than two strings (n > 25) , you might want to think something else.

''.join([a, b, c]) trick is a performance optimization.

于 2012-04-06T12:46:04.690 回答
8

与多人一起工作时,有时很难确切地知道发生了什么。使用格式字符串而不是串联可以避免一个特殊的烦恼,这种烦恼对我们来说已经发生了很多次:

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

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

于 2012-04-06T14:28:57.400 回答
8

永远不应该使用 + 进行字符串连接,而是始终使用 ''.join 的假设可能是一个神话。确实, using+创建了不可变字符串对象的不必要的临时副本,但另一个不经常引用的事实是,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. 串联操作数。

并且在开发中过早的优化是邪恶的。

于 2012-04-06T13:24:42.640 回答
4

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

CPython 实现细节:如果 s 和 t 都是字符串,一些 Python 实现(例如 CPython)通常可以对 s = s + t 或 s += t 形式的赋值执行就地优化。如果适用,这种优化会大大降低二次运行时间的可能性。这种优化既依赖于版本,也依赖于实现。对于性能敏感的代码,最好使用 str.join() 方法,该方法可确保跨版本和实现的线性连接性能一致。

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

于 2017-01-04T12:49:41.843 回答
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) 时间。

所以至少在性能方面,使用+是好的。

于 2012-04-06T13:28:06.730 回答
3

我将以下内容与 python 3.8 一起使用

string4 = f'{string1}{string2}{string3}'
于 2020-04-18T15:04:38.663 回答
1

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

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

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

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

于 2016-04-29T12:34:28.020 回答