Python 的 int 为什么不会溢出?拆开 PyLongObject 和它背后那串变长数组

简介: 很多人背过“字典查询是 O(1)”,但为什么是常数?CPython 3.13 实测 dict.get 在 100→10 万规模恒为 0.1 微秒,而 list.index 线性涨到 609 微秒(慢 4743 倍)。本文拆开开放寻址哈希表、扰动函数与 2/3 扩容阈值把 O(1) 钉死,也讲清“创建是 O(n)”这个常被混的点。

你有没有试过在 Python 里算 2 ** 10000?它不会像 C 那样默默变成 0,也不会抛异常,而是老老实实吐出一长串数字。很多人说"Python 的 int 没有上限",这句话对,但它真正的代价藏在内存里。今天我把 CPython 的整数对象拆开,看看它为了"永不溢出"到底付出了什么。

一、先说一个反直觉的小事实

在 Python 启动的那一刻,解释器就偷偷把 -5 到 256 这 262 个整数全部预先建好缓存起来了。你可以验证:

a = 256
b = 256
print(a is b)   # True,指向同一个对象
x = 257
y = 257
print(x is y)   # 在多数交互环境里是 False,每次都是新对象

这意味着"小整数"几乎不占额外内存,而且比较 is 还能命中同一个对象。这一个细节,是很多人优化热点循环时忽略的第一点——反复创建 257 以上的小整数,其实在不断 malloc 新对象。

二、PyLongObject 到底是什么

CPython 源码里,整数对象的核心是这么个结构(简化):

struct _longobject {
   
    PyObject_VAR_HEAD
    int ob_digit[1];   // 变长数组,每一位存 30 个二进制位
};

关键点有三个,我一个一个说:

  1. ob_size 同时编码"长度"和"符号"。它是负数表示这个整数是负的,绝对值才是数组长度。一个字段干了两件事,这是 C 里常见的省内存技巧。
  2. ob_digit 不是按"字节"存的,而是按 30 位(64 位平台上)一块一块存。也就是说,一个 digit 是 30 个二进制位,而不是 8 个。这样做是为了在 32 位整型里留足余量做乘法进位,避免溢出。
  3. 数组是变长的。小数字只需要 1 个 digit,大数字就不断追加。这就是"永不溢出"的真相——不够就加 digit,直到内存用完。

三、实测:一个整数到底占多少内存

我在一台 Python 3.13(64 位)上跑了 sys.getsizeof,看不同量级整数占用的字节:

数值 二进制位数 估算 digit 数 实际 sizeof
0 0 0 28 B
255 8 1 28 B
2**30 31 2 32 B
2**60 61 3 36 B
2**100 101 4 40 B
2**525 525 18 96 B
2**1000 1001 34 160 B

注意看:2**1000 是 1001 个二进制位,理论上 1001/8 ≈ 126 字节就够了,但实际是 160 字节。多出来的部分,一部分是 30 位对齐的浪费(每个 digit 实际占 4 字节即 32 位,有 2 位闲着),另一部分是对象头 PyObject_VAR_HEAD 的固定开销(28 字节起)。

我个人的判断是:这个设计在"小整数"上极度省(28 字节封顶),但在"几百位以上的大整数"上,相对理论最小编码有约 20%~30% 的冗余。对绝大多数业务来说无所谓,但如果你在内存里塞了几百万个大素数,这个冗余就值得换方案了(比如直接用 gmpy2 的 mpz)。

四、和别的语言比一比

类型 上限 溢出行为 内存
C long (64位) ±9.2×10¹⁸ 静默回绕 8 字节固定
Java BigInteger 无限 不溢出 变长,类似思路
Python int 无限 不溢出 28B 起,变长

C 快但会溢出(还静默),Java 和 Python 都走"变长"路线保证正确,代价就是慢一点、占内存多一点。

五、三个最容易踩的坑

  1. 别拿 is 比较大整数。只有 -5~256 有缓存,超出范围 is 结果不可靠,老老实实用 ==。
  2. 大整数运算慢。每一位都要做 30 位乘法和进位处理,2**100000 的乘法比你想的贵得多。
  3. sys.getsizeof 只算对象本身,如果你把大整数放进列表,列表里存的只是指针,整数对象的内存是另算的——这点做内存评估时经常漏。

结语

Python 的 int 不会溢出,不是魔法,是"用内存换正确性"的取舍:一个 30 位的变长数组 + 复用的对象头。小数字几乎白送,大数字按位计费。理解这一点,你就不会再奇怪为什么 2**1000 是 160 字节,也不会再误用 is 去比较整数了。

相关文章
|
云安全 安全 网络安全
|
7天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2018 1
|
15天前
|
JSON 程序员 数据格式
最新版Notepad++下载、安装、配置保姆级教程(附官网安装包)
Notepad++ 是一款免费开源、轻量快速的 Windows 文本编辑器,支持多标签、语法高亮、代码折叠与宏录制,体积小、启动快、资源占用低,兼容 x64/x32/ARM 架构,是程序员和文字工作者的高效利器。
3374 0
|
14天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
2365 44
|
11天前
|
Web App开发 人工智能 前端开发
2026 年 AI 浏览器怎么选?Ego Lite、Tabbit、Chrome DevTools MCP 深度对比,开发者到底该用哪个?
2026年AI浏览器选型指南:Ego Lite专注多Agent协同(Mac优先),Tabbit国产首选,原生支持Windows与Codex控制;Chrome DevTools MCP非浏览器而是开发者利器,深度集成Console/Network/Performance调试能力,让AI真正“看懂并验证”前端代码。
1447 3
|
8天前
|
安全 应用服务中间件 Go
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
PHP2Shell 双 RCE 已遭在野利用:WordPress、Drupal 全在射程内,nginx 其实另有洞。拆成因与修复顺序
PHP2Shell 漏洞拆解:WordPress 与 nginx 为何中招
|
8天前
|
开发者 索引
DeepSeekHarness大肥鱼自带插件市场了?加个地址就能用
Open Plugin Market 是一个开放插件索引平台,收录800+优质DSH插件,支持分类浏览、智能推荐与随机发现。无需安装市场,粘贴索引地址即可一键接入,网页版直访 dshopen.org,助力小开发者被看见。(239字)
1835 1
DeepSeekHarness大肥鱼自带插件市场了?加个地址就能用
|
6天前
|
人工智能 Linux API
Magpie, 轻量本地AI网关!免费Token用不完!
Magpie 是开源AI编程模型调度网关,上线一月获1.8万Star。它通过本地高性能网关统一托管多平台API密钥、账号与免费额度,实现跨Agent的模型秒级切换、静默额度轮换与资源复用,显著降低碎片化管理成本。(239字)
Magpie, 轻量本地AI网关!免费Token用不完!
|
6天前
|
安全 程序员 应用服务中间件
PHP 被曝两大致命漏洞,请所有程序员抓紧做这件事!
PHP 官方发布了紧急安全更新,一口气修复了两个严重漏洞。如果网站和服务器用了 PHP 应该怎么处理?漏洞的原理是什么?本文给你讲清楚
720 0