Python字符串为不可变类型,为何使用+=追加内容时id保持不变?
为什么Python字符串是不可变类型,
+=拼接时id却保持不变? 首先明确核心前提:Python的字符串确实是严格的不可变类型,对象创建后内存里存储的值绝对不会被直接修改,你观察到的两种id表现差异,完全是CPython解释器的内部优化策略导致的,和字符串可不可变没有冲突。
+=循环追加时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不变是CPython专门做的增量赋值优化:当解释器发现你用+=/*=对字符串做增量拼接,且这个字符串对象没有被其他变量引用时,就不会走“申请新内存、拷贝原内容+新内容、销毁旧对象”的通用流程,而是直接给原有字符串的内存空间扩容,把新追加的内容写到扩容后的位置。
这个优化完全不会破坏不可变类型的约定:因为没有其他变量指向这块内存,没有任何代码能感知到内存的变化,你拿到的始终是同一个对象的引用,id自然不会变。
普通+拼接时id变化的原因
对应的现象代码如下:
>>> a = "hello" >>> id(a) 139841228475760 >>> a = "b" + a[1:] >>> print(a) bello >>> id(a) 139841228475312
这里id变化是因为你用的是普通+运算,不是增量赋值:
执行"b" + a[1:]时,解释器会先拿到两个独立的字符串对象——字面量"b",以及切片生成的新字符串"ello",之后会申请一块全新的内存,把两个字符串的内容拷贝进去生成新的"bello"对象,最后把变量a重新绑定到这个新对象上。新对象和原来的"hello"根本不是同一块内存,id当然不一样。
两个需要注意的误区
- 这个原地扩容的优化是CPython的专属实现,不是Python语言规范强制要求的,PyPy、Jython等其他解释器不一定有这个行为,写业务代码时绝对不要依赖“
+=拼接字符串id不变”这个特性。 - 如果字符串有其他引用,就算用
+=也不会触发原地优化。比如你写s1 = "abc"; s2 = s1; s1 += "d",执行完s1和s2的id一定不同——毕竟s2还指向原来的"abc",如果直接修改原内存,s2的值也会变,就彻底打破了字符串不可变的规则。
内容的提问来源于stack exchange,提问作者Master_Roshy
相关产品推荐
相关产品推荐

