25 分钟
Python 高级

整数与字符串的底层实现

理解小整数缓存池、大整数存储、字符串驻留和紧凑表示

  • 理解小整数缓存池机制
  • 理解大整数的 ob_digit 数组存储
  • 理解字符串的紧凑表示和驻留机制
  • 能解释 is 和 == 的区别

整数与字符串的底层实现

理解 Python 数据类型的底层实现,能帮你写出更高效的代码,也能解释很多「反直觉」的现象。这节课我们深入整数和字符串。

小整数缓存池

CPython 启动时就创建了 -5 到 256 的整数对象,这些是单例——整个程序里同一个数字就是同一个对象:

示例代码(可运行)
⚠️常见面试题

a = 256; b = 256; a is b → True。a = 257; b = 257; a is b → 取决于执行环境。在 .py 文件中编译器会缓存常量,所以可能是 True;在交互式 REPL 中是 False。不要依赖这个行为,比较值用 ==。

大整数的存储

Python 的 int 是任意精度的。大整数内部用一个 C 数组(ob_digit)存储,每个元素存 30 位二进制。

示例代码(可运行)
🐍资深工程师经验谈

大整数运算比小整数慢得多,因为需要处理数组。加法要逐组进位,乘法用 Karatsuba 算法(O(n1.585))替代朴素 O(n2)。性能敏感场景,能用小整数就别用大整数。比如数据库 ID 用 int 足够(21亿上限),不需要随便用更大的数。

字符串的紧凑表示

Python 3.3+ 引入了灵活字符串表示(PEP 393):根据字符串内容自动选择最节省内存的编码。

示例代码(可运行)

字符串驻留(Interning)

Python 会缓存某些字符串,相同的字符串字面量会共享同一个对象。

示例代码(可运行)
💡性能提示

字符串不可变带来的好处:线程安全、可以安全驻留共享、hash 值可以缓存。代价是拼接效率低。大量拼接用 list + join,或用 io.StringIO。字典键总是用不可变类型(str/int/tuple)。

CPython源码

小整数缓存的源码级理解

CPython 中小整数缓存的实现非常直接:

C / C++
// CPython 源码(简化)
// Objects/longobject.c
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
// NSMALLNEGINTS = 5, NSMALLPOSINTS = 257
// 即 small_ints[0] 对应 -5, small_ints[261] 对应 256(下标 = 值 + 5)

// 当你写 100 时,CPython 直接返回 &small_ints[105]
// 不需要分配内存,不需要初始化
// 这就是为什么小整数操作特别快

这个缓存池在解释器启动时初始化,整个生命周期不释放。

选择题

a = 256; b = 256; a is b 的结果是?

选择题

大量字符串拼接最高效的方式是?

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

@decorator 写在函数定义上方,它在「模块加载、函数被定义时」就执行一次,把原函数包装成新函数;之后每次调用,走的都是包装后的版本。带参数的装饰器本质是三层嵌套(外层收参数、中层收函数、内层收调用参数)。务必用 functools.wraps 保留原函数名和文档,否则调试栈和日志里全是 wrapper。

挑战任务

整数缓存验证器

简单+50 XP

写一个函数,验证哪些整数是被缓存的(is 比较为 True),输出缓存范围。

整数缓存验证器
2 个测试用例

课后作业

字符串内存分析

中等+25 XP

写一个函数,分析不同字符串的内存占用,验证 ASCII 比中文省内存。

字符串内存分析
1 个测试用例