小整数池(-5 到 256)对象在 cpython 启动时静态分配并复用,其 id 是稳定内存地址而非递增序号;超出该范围的整数每次创建可能获得不同地址,id() 仅反映当前内存位置,不可用作唯一标识或创建顺序依据。

id() 不是从“小整数池”开始分配的——它只是对小整数对象返回了预先分配好的内存地址,而这些地址本身不一定是数值上“小”的整数。
你看到 id(1)、id(256) 等返回一串看似规律的数字(比如 1402345678912),但那不是“从小开始递增的 ID 序列”,而是 CPython 将小整数对象(-5 到 256)在解释器启动时就静态分配在内存固定区域的结果。它们的 id 值稳定,是因为对象本身早就存在、复用,而不是因为 ID 分配器“优先给小整数派小编号”。
小整数池里的对象在启动时就已驻留内存
Python 解释器一启动,就会预创建 -5 到 256 范围内所有整数对象,并把它们放在一块固定的内存区域里。后续代码中只要写 a = 10 或 b = 256,CPython 就直接让变量指向那个早已存在的对象,不 new、不 malloc。
- 这些对象的
id是其真实内存地址,所以稳定可预测 - 它们不会被垃圾回收,生命周期与解释器一致
- 你反复执行
id(10),结果总是一样;但id(257)每次都可能不同(取决于内存分配时机)
>>> id(10) 1402345678912 >>> id(10) # 同一进程内,始终一样 1402345678912 >>> id(257) 1402345680144 >>> id(257) # 可能变(尤其在交互式环境多次赋值后) 1402345680272
id() 的本质是内存地址,不是逻辑序号
id() 在 CPython 中就是对象的内存起始地址(uintptr_t 类型),它由操作系统和内存管理器决定,不是 Python 自己维护的自增计数器。
- 对小整数:地址来自静态池,所以“看起来有序”
- 对大整数/列表/字符串:地址来自堆分配,受 ASLR、内存碎片、对象生命周期影响,毫无规律
- 对已删除又重建的对象:地址可能复用(尤其短生命周期对象)
常见误解:id() 小 → 对象“更早创建”或“更基础”。错。它只说明:这个对象当前在哪儿。哪怕你先定义 x = 257 再定义 y = 10,id(y) 依然远小于 id(x) —— 因为 10 的对象早就躺在内存里了。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
为什么选 [-5, 256] 这个范围?
这不是数学推导出来的,而是实证优化结果:
- 统计表明,程序中绝大多数整数操作集中在这一区间(循环索引、布尔转换、ASCII 码、状态码等)
- 扩大范围会浪费内存(比如预建 10 万个整数对象);缩小则缓存命中率骤降
-
-5是为了覆盖常见的负偏移(如list[-1]、range(-5, 0)) -
256覆盖一个字节的无符号取值范围,也适配很多底层协议和编码场景
注意:这个范围是 CPython 的实现细节,不是语言规范。PyPy、Jython 等不一定有相同行为;即使在 CPython 中,编译选项或调试模式也可能影响它。
容易踩的坑:拿 id() 当唯一标识或序列号用
-
id()复用:对象销毁后,新对象可能拿到旧id(尤其在循环中快速创建销毁小对象时) -
id()无跨进程/跨解释器意义:两个 Python 进程里id(1)可能一样,也可能不一样,完全不可比 -
id()和可哈希性无关:小整数能当字典 key 是因为hash()实现固定,不是因为id()稳定
真正需要唯一标识时,该用 uuid.uuid4();要判断是否同一对象,用 is 就够了;想记录创建顺序,自己加计数器字段。
真正容易被忽略的是:小整数池的存在,让 is 在 -5 区间“碰巧成立”,但一旦超出,哪怕值相等,<code>is 就大概率失效——这不是 bug,是设计使然,也是你写 if x is True: 而不是 if x is 1: 的底层原因之一。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










