在Python中附加到字符串的正确方法

Éti*_*nne 3 python

我读过这个回复,它解释了 CPython 有一个优化,可以在使用a = a + bor附加到字符串时执行就地附加,无需复制a += b。我还阅读了这个 PEP8 建议:

\n
\n

代码的编写方式不应损害其他 Python 实现(PyPy、Jython、IronPython、Cython、Psyco 等)。例如,不要依赖 CPython\xe2\x80\x99 对形式 a += b 或 a = a + b 中的语句进行就地字符串连接的高效实现。即使在 CPython 中,这种优化也是脆弱的(它仅适用于某些类型),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应使用\'\'.join() 形式。这将确保在各种实现中以线性时间进行串联。

\n
\n

因此,如果我理解正确,a += b + c正确的方法是调用a = \'\'.join([a, b, c])?

\n

但是为什么这个形式join比本例中的形式慢得多+=(在loop1中我故意使用a = a + b + c以避免触发CPython优化)?

\n
import os\nimport time\n\nif __name__ == "__main__":\n    start_time = time.time()\n    print("begin: %s " % (start_time))\n    s = ""\n    for i in range(100000):\n        s = s + str(i) + \'3\'\n    time1 = time.time()\n    print("end loop1: %s " % (time1 - start_time))\n\n    s2 = ""\n    for i in range(100000):\n        s2 += str(i) + \'3\'\n\n    time2 = time.time()\n    print("end loop2: %s " % (time2 - time1))\n\n    s3 = ""\n    for i in range(100000):\n        s3 = \'\'.join([s3, str(i), \'3\'])\n\n    time3 = time.time()\n    print("end loop3: %s " % (time3 - time2))\n
Run Code Online (Sandbox Code Playgroud)\n

结果显示join在这种情况下速度明显慢:

\n
~/testdir$ python --version\nPython 3.10.6\n~/testdir$ python concatenate.py \nbegin: 1675268345.0761461 \nend loop1: 3.9019 \nend loop2: 0.0260 \nend loop3: 0.9289 \n
Run Code Online (Sandbox Code Playgroud)\n

难道是我的版本join不对?

\n

Jon*_*nSG 7

join()在“loop3”中,您通过以不需要的方式连续调用它而绕过了很多增益。最好join()一次性建立完整的字符列表。

\n

查看:

\n
import time\n\niterations = 100_000\n\n##----------------\ns = ""\nstart_time = time.time()\nfor i in range(iterations):\n    s = s + "." + \'3\'\nend_time = time.time()\nprint("end loop1: %s " % (end_time - start_time))\n##----------------\n\n##----------------\ns = ""\nstart_time = time.time()\nfor i in range(iterations):\n    s += "." + \'3\'\nend_time = time.time()\nprint("end loop2: %s " % (end_time - start_time))\n##----------------\n\n##----------------\ns = ""\nstart_time = time.time()\nfor i in range(iterations):\n    s = \'\'.join([s, ".", \'3\'])\nend_time = time.time()\nprint("end loop3: %s " % (end_time - start_time))\n##----------------\n\n##----------------\ns = []\nstart_time = time.time()\nfor i in range(iterations):\n    s.append(".")\n    s.append("3")\ns = "".join(s)\nend_time = time.time()\nprint("end loop4: %s " % (end_time - start_time))\n##----------------\n\n##----------------\ns = []\nstart_time = time.time()\nfor i in range(iterations):\n    s.extend((".", "3"))\ns = "".join(s)\nend_time = time.time()\nprint("end loop5: %s " % (end_time - start_time))\n##----------------\n
Run Code Online (Sandbox Code Playgroud)\n

需要明确的是,您可以使用以下命令运行它:

\n
iterations = 10_000_000\n
Run Code Online (Sandbox Code Playgroud)\n

如果您愿意,请务必删除“loop1”和“loop3”,因为它们在大约 300k 后会变得非常慢。

\n

当我运行 1000 万次迭代时,我看到:

\n
end loop2: 16.977502584457397 \nend loop4: 1.6301295757293701 \nend loop5: 1.0435805320739746\n
Run Code Online (Sandbox Code Playgroud)\n

所以,显然有一种join()快速使用的方法:-)

\n

附录:

\n

@\xc3\x89tienne 建议使字符串附加更长的时间会逆转结果,并且除非在函数中,否则不会发生循环 2 的优化。我没有看到相同的情况。

\n
end loop2: 16.977502584457397 \nend loop4: 1.6301295757293701 \nend loop5: 1.0435805320739746\n
Run Code Online (Sandbox Code Playgroud)\n

在 python 3.10 和 3.11 上结果相似。我得到如下结果:

\n
end loop2: 336.98531889915466 \nend loop4: 1.0211727619171143 \nend loop5: 1.1640543937683105\n
Run Code Online (Sandbox Code Playgroud)\n

这继续向我表明速度join()要快得多。

\n

  • 这是一个非常消耗内存的程序。列表“s”最后将有 200000 个条目。 (2认同)