你好,我是 hockor,上一家我们讲了 Python 中基础数据类型的字符串和数字相关的内容,今天,我们来聊聊 Python 中两个与内存优化相关的有趣机制:字符串驻留 (String Interning) 和 小整数池 (Small Integer Pool)。
字符串驻留机制
什么是字符串驻留?
简单来说,当你创建某些符合特定条件的字符串时,Python 会检查内存中是否已存在一个具有相同值的字符串。如果存在,新的变量名会直接指向这个已存在的内存地址,而不是重新创建一个新的字符串对象。
为什么需要字符串驻留?
-
节省内存:显而易见,如果大量重复的短字符串只存储一份,内存占用会显著降低。
-
提高比较效率:当比较两个驻留的字符串是否相等时,Python 可以直接比较它们的内存地址(使用 is 运算符)。地址比较远比逐个字符比较要快得多。
哪些字符串会被驻留?
Python 中字符串驻留的规则比较微妙,但通常包括:
-
编译时常量字符串:在代码中直接写出来的字符串字面量,例如 s = "hello"。
-
短字符串:长度较短的字符串更容易被驻留。具体长度阈值可能因 Python 版本和实现而异。
-
符合标识符规则的字符串:由字母、数字和下划线组成的字符串(例如,用作字典键或对象属性名的字符串)通常会被自动驻留。
-
显式驻留:可以使用 sys.intern() 函数强制驻留一个字符串。
is 和 == 运算符
在 JS 中一般我们没办法看到值的具体内存地址,一般我们就使用 三等号 判断两个变量是否类型相同/值相同/引用相同。
但是在 Python 中呢,给我们提供了一个 id
函数,它可以打印出对象的唯一标识符, 同时 Python 还有个 == 运算符,有点类似于咱们 JS 中的双等号。
所以简单理解,你可以认为 Python 的 == 运算符类似于 js 中的双等号,is 类似于 JS 中的三等号(但是他们本质还是有区别的)

简单总结如下:
is 运算符
- 用于身份比较(identity comparison)
- 检查两个对象是否是同一个内存对象
- 比较的是对象的内存地址
- 可以使用 id() 函数查看对象的内存地址
== 运算符
- 用于值比较(value comparison)
- 检查两个对象的值是否相等
- 比较的是对象的内容
示例代码
这里我们来看如下代码示例(还记得字符串前面的 f 吗,是 f-string 格式化的标识符哦)
python
# 示例 1: 字面量字符串
a = "hello_python"
b = "hello_python"
print(f"a: '{a}', id(a): {id(a)}")
print(f"b: '{b}', id(b): {id(b)}")
print(f"a is b: {a is b}") # 通常为 True,因为 "hello_python" 是字面量且符合标识符规则
# 示例 2: 拼接产生的字符串
c = "hello_" + "python" # 编译期可能优化为 "hello_python"
print(f"c: '{c}', id(c): {id(c)}")
print(f"a is c: {a is c}") # 通常也为 True
# 示例 3: 动态创建的字符串
x = "world"
d = "hello_" + x # 运行时拼接
e = "hello_" + x
print(f"d: '{d}', id(d): {id(d)}")
print(f"e: '{e}', id(e): {id(e)}")
print(f"d is e: {d is e}") # 通常为 False,因为 d 和 e 是运行时动态创建的
# 示例 4: 使用 sys.intern() 强制驻留
import sys
f = sys.intern("hello_" + x)
g = sys.intern("hello_" + x)
print(f"f: '{f}', id(f): {id(f)}")
print(f"g: '{g}', id(g): {id(g)}")
print(f"f is g: {f is g}") # True,因为显式驻留
打印结果:

对比JS
从我们前端视角看,JavaScript 引擎通常也会对字符串字面量进行优化,使得相同的字符串字面量指向同一内存位置。这与 Python 的编译时常量字符串驻留有相似之处。然而,JavaScript 并没有像 Python sys.intern() 这样提供给开发者直接控制字符串驻留的明确接口。
对于动态生成的字符串,例如
js
let dynStr1 = "a" + "b";
let dynStr2 = "a" + "b";,
引擎是否会进行类似的优化是其内部实现细节,不像 Python 那样有明确的规则和工具。
在 Python 中,is 关键字用于比较两个对象的内存地址是否相同(即它们是否是同一个对象),而 == 用于比较两个对象的值是否相等。对于驻留的字符串,is 和 == 都会返回 True。
简单说,如果 x is y 成立的话,x 一定等于 y
。
小整数池
与字符串驻留类似,Python 为了效率,对一定范围内的整数也进行了特殊处理,预先创建并缓存了它们的对象。这个缓存就是所谓的小整数池。
其原理是 Python 解释器在启动时,会预先创建一定范围内的整数对象。当你代码中使用到这个范围内的整数时,Python 不会创建新的整数对象,而是直接返回缓存中已存在的对象的引用。
预缓存的范围是?
这个范围通常是 [-5, 256] (包含 -5 和 256)。也就是说,从 -5 到 256 的整数,在 Python 中都是单例的。
为什么需要小整数池?
-
性能提升:这些小整数非常常用,预先缓存可以避免频繁地创建和销毁对象,从而提高性能。
-
节省内存:同样,对于这些常用整数,只保留一份实例可以节省内存。
代码示例
python
# 示例 1: 在小整数池范围内
num1 = 100
num2 = 100
print(f"num1: {num1}, id(num1): {id(num1)}")
print(f"num2: {num2}, id(num2): {id(num2)}")
print(f"num1 is num2: {num1 is num2}") # True
num3 = -5
num4 = -5
print(f"num3: {num3}, id(num3): {id(num3)}")
print(f"num4: {num4}, id(num4): {id(num4)}")
print(f"num3 is num4: {num3 is num4}") # True
num5 = 256
num6 = 256
print(f"num5: {num5}, id(num5): {id(num5)}")
print(f"num6: {num6}, id(num6): {id(num6)}")
print(f"num5 is num6: {num5 is num6}") # True
# 示例 2: 超出小整数池范围
num7 = 257
num8 = 257
print(f"num7: {num7}, id(num7): {id(num7)}")
print(f"num8: {num8}, id(num8): {id(num8)}")
print(f"num7 is num8: {num7 is num8}") # 通常为 False (但在某些交互式环境或特定优化下可能为True)
num9 = -6
num10 = -6
print(f"num9: {num9}, id(num9): {id(num9)}")
print(f"num10: {num10}, id(num10): {id(num10)}")
print(f"num9 is num10: {num9 is num10}") # 通常为 False
# 注意:对于超出范围的整数,在某些Python解释器或IDE的交互式环境中,
# 为了优化,可能会对同一行或紧邻行定义的相同数字进行缓存,导致 `is` 返回 True。
# 但在脚本执行时,超出 [-5, 256] 范围的整数通常不会是同一个对象。
a = 1000
b = 1000
print(f"a=1000, b=1000, a is b: {a is b}") # 通常为 False
# 但如果这样写,有些解释器会优化:
c, d = 1000, 1000
print(f"c=1000, d=1000 (同一行), c is d: {c is d}") # 可能为 True
运行结果:

对比JS
JavaScript 中的数字类型 (Number) 遵循 IEEE 754 双精度浮点数标准。它不像 Python 那样有一个明确定义的、语言规范层面的"小整数池"。
js
let n1 = 100;
let n2 = 100;
console.log(n1 === n2); // true (值比较)
// JavaScript 中没有 `is` 这样的身份运算符来直接比较原始值的内存地址
// (因为原始值不是对象,它们的值直接存储在变量访问的位置)。
// 对于对象,`===` 比较的是引用。
let obj1 = { val: 100 };
let obj2 = { val: 100 };
let obj3 = obj1;
console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
虽然 JavaScript 语言本身没有小整数池,但现代 JavaScript 引擎(如 V8)内部会有很多优化。例如,V8 引擎对小整数(SMIs - Small Integers)有特殊的、更高效的表示和处理方式,以提升性能。但这属于引擎的内部实现细节,对开发者来说是不透明的,不像 Python 的小整数池那样是一个可观察和依赖的语言特性。
总结
今天我们主要是讲到了 Python 的字符串驻留机制和小整数池,是为了优化性能和内存使用而采用的两种策略。
-
字符串驻留:通过共享不可变字符串的内存,减少内存占用并加快比较速度。
-
小整数池:预先缓存常用的小整数对象(通常是 -5 到 256),避免重复创建,提升效率。
在 Python 中,过度依赖 is 来比较字符串或数字的值(尤其是超出小整数池范围的数字)可能导致意外的行为。理解其背后的原理能帮助避免这类错误。始终记住,== 用于值比较,is 用于身份比较。
以上是我们今天的内容,我们下一节再见👋