You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 11:39:19