Python中的字符串是不可变的,这意味着该值无法更改。但是,在以下示例中追加到字符串时,由于 id 保持不变,因此原始字符串内存似乎已被修改:
>>> s = 'String' >>> for i in range(5, 0, -1): ... s += str(i) ... print(f"{s:<11} stored at {id(s)}") ... String5 stored at 139841228476848 String54 stored at 139841228476848 String543 stored at 139841228476848 String5432 stored at 139841228476848 String54321 stored at 139841228476848
相反,在以下示例中,id 会更改:
>>> a = "hello" >>> id(a) 139841228475760 >>> a = "b" + a[1:] >>> print(a) bello >>> id(a) 139841228475312118
这是一个特定于CPython的优化,适用于被追加到碰巧没有其他活引用的情况。在这种情况下,解释器“作弊”,允许它通过重新分配(可以就位,具体取决于堆布局)和直接附加数据来修改现有字符串,并且经常在重复连接的循环中显着减少工作(使其行为更像a的摊销追加而不是每次复制操作)。除了未更改的之外,它没有可见的效果,因此这样做是合法的(除非在逻辑上被替换,否则具有现有引用的人都不会看到它发生变化)。str
O(1)
list
O(n)
id
str
str
你实际上不应该依赖它(非引用计数的解释器不能使用这个技巧,因为他们不知道是否有其他引用),根据PEP8的第一个编程建议:str
代码的编写方式不应使Python的其他实现(PyPy,Jython,IronPython,Cython,Psyco等)不利。
例如,不要依赖 CPython 为 form 或 中的语句有效实现就地字符串串联。即使在CPython中,这种优化也很脆弱(它仅适用于某些类型),并且在不使用refcounting的实现中根本不存在。在库的性能敏感部分,应改用表单。这将确保在各种实现中以线性时间进行串联。
a += b
a = a + b
''.join()
如果你想打破优化,有各种各样的方法可以做到这一点,例如,将你的代码更改为:
>>> while i!=0: ... s_alias = s # Gonna save off an alias here ... s += str(i) ... print(s + " stored at " + str(id(s))) ... i -= 1 ...
通过创建别名,增加引用计数并告诉Python更改将在 其他位置可见来破坏它,因此它无法应用它。类似地,像这样的代码:s
s = s + a + b
不能使用它,因为首先发生,并产生一个临时的,然后必须添加到,而不是立即替换,并且优化太脆而无法尝试处理。几乎相同的代码,如:s + a
b
s
s += a + b
艺术
s = s + (a + b)
通过确保最终串联始终是左操作数的位置,并且结果用于立即替换 来恢复优化。