【问题标题】:Why is it so slow when assigning a concatenated string to a variable in python?为什么将连接字符串分配给python中的变量时会这么慢?
【发布时间】:2019-09-28 21:17:31
【问题描述】:

如果只是如下的字符串拼接,则立即结束。

test_str = "abcdefghijklmn123456789"
str1 = ""
str2 = ""

start = time.time()
for i in range(1, 100001):

    str1 = str1 + test_str
    str2 = str2 + test_str

    if i % 20000 == 0:
        print("time(sec) => {}".format(time.time() - start))
        start = time.time()

恒定的处理时间

time(sec) => 0.013324975967407227
time(sec) => 0.020363807678222656
time(sec) => 0.009979963302612305
time(sec) => 0.01744699478149414
time(sec) => 0.0227658748626709

莫名其妙地,将连接的字符串分配给另一个变量会使过程越来越慢。

test_str = "abcdefghijklmn123456789"
str1 = ""
str2 = ""

start = time.time()
for i in range(1, 100001):

    str1 = str1 + test_str
    # str2 = str2 + test_str
    # ↓
    str2 = str1

    if i % 20000 == 0:
        print("time(sec) => {}".format(time.time() - start))
        start = time.time()

处理时间会有所延迟。

time(sec) => 0.36466407775878906
time(sec) => 1.105351209640503
time(sec) => 2.6467738151550293
time(sec) => 5.891657829284668
time(sec) => 9.266698360443115

python2 和 python3 给出相同的结果。

【问题讨论】:

  • 因为您使用的是二次时间算法。解释器能够对此进行优化,但仅限于某些情况。你不应该依赖它,而是使用线性时间算法(通常,附加到列表然后''.join
  • 根据@shadowranger的回答进行了验证,找到了原因。如果只使用字符串拼接,str1和str2的id保持不变,但是如果str1赋值给str2,str1的id每次都会改变。换句话说,每次都分配 str1 会减慢它的速度。
  • common sequence operations 文档中的注释 6 明确指出此行为是二次的。

标签: python append big-o string-concatenation


【解决方案1】:

一般来说,Python 语言标准在这里不做任何保证;事实上,正如定义的那样,字符串是不可变的,您正在做的事情应该对您不利,因为您已经编写了Schlemiel the Painter's algorithm 的形式。

但在第一种情况下,作为实现细节,CPython(参考解释器)将帮助您,并在一些相当具体的条件下将字符串连接到位(技术上违反不变性保证),使其能够遵守不变性规则的精神。最重要的条件是被连接的字符串必须只在一个地方被引用(如果不是,另一个引用会在原地改变,违反str 不可变的外观)。通过在每次连接后分配 str2 = str1,您可以保证连接时有 两个 引用,因此每个连接都必须创建一个新的 str 以保持明显的不变性的字符串。这意味着更多的内存分配和释放,更多(并且逐渐增加)内存副本等。

请注意,PEP 8, the Python style guide 明确不鼓励依赖此优化:

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

    例如,对于a += ba = a + b 形式的语句,不要依赖CPython 对就地字符串连接的高效实现。即使在 CPython 中,这种优化也很脆弱(它只适用于某些类型),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应使用''.join() 形式。这将确保在各种实现中以线性时间发生连接。

关于“仅适用于某些类型”的注释很重要。此优化仅适用于str;在 Python 2 中它不适用于 unicode(尽管 Python 3 的 str 是基于 Python 2 的 unicode 的实现),而在 Python 3 中它不适用于 bytes(它们是相似的到 Python 2 的 str 引擎盖下)。

【讨论】:

  • 这正是我所怀疑的。
  • "所以每个连接都必须创建一个新的 str 以保持字符串的明显不变性。" => 这正是原因。谢谢!!
  • 在字符串不可变的语言中,通常有另一种有效构建字符串的方法。见Python string class like StringBuilder in C#?
  • @uma66 如果有帮助,您可以尝试接受答案。
猜你喜欢
  • 1970-01-01
  • 2011-12-26
  • 2019-02-25
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
  • 2011-07-02
  • 2016-02-10
  • 2022-10-05
相关资源
最近更新 更多