Python 对象模型、int 与 CPython 实现
沿着 Python 对象模型、int 与 CPython 内部结构,连接语言现象和底层实现。
建议先了解
- Python 函数与类
- C 结构体基础
1. 这篇笔记解决什么问题
本篇把本次对话中最核心的一条 Python 主线串起来:
Everything is object
↓
模块、函数、类、方法都有属性
↓
int 也是对象
↓
int 不可变
↓
CPython 用 PyLongObject 实现整数
↓
小整数缓存与对象生命周期
学完后应能回答:函数和方法为什么有属性;Python 中什么不是对象;x = 10 到底是什么意思;x = x + 1 时原来的 10 怎么处理;Python 为什么能表示超大整数;CPython 为什么预创建小整数;安装 Python 后这些 C 实现代码在哪里。
2. 前置知识
- Python 内置机制、模块与文档字符串
- C 指针与结构体基本概念。
待补充:descriptor、引用计数、Python allocator 的完整实现。
3. 核心概念
3.1 Everything is object
Python 中绝大多数可作为运行时“值”存在的东西都统一为对象:
整数 → int 对象
字符串 → str 对象
列表 → list 对象
函数 → function 对象
类 → type/class 对象
模块 → module 对象
类型本身 → 对象
但语法结构本身不是普通对象,例如:
if / for / while
+ / - / =
调用括号 ()
属性访问符 .
赋值语句本身
x = 10 中:
x是名字;=是赋值语法;10是int对象;- 执行后形成名字到对象的绑定。
3.2 函数和方法也有属性
def add(a, b):
"""Add two numbers."""
return a + b
add 是函数对象,可以访问:
add.__name__
add.__doc__
add.__module__
add.__annotations__
add.__code__
普通 Python 函数对象还可以附加自定义属性:
add.author = "Kening"
类中:
class Person:
def hello(self):
return "hello"
Person.hello 是类上的函数;p.hello 通常是绑定到实例的 bound method,可理解为保存:
p.hello
├── __self__ → p
└── __func__ → Person.hello
3.3 int 是类型对象,10 是实例
type(int)
# <class 'type'>
type(10)
# <class 'int'>
isinstance(10, int)
# True
关系:
type
↑
int
↑
10, 20, -5 ...
3.4 名字绑定与不可变性(Immutability)
x = 10
更适合理解为:
名字 x ─────→ int 对象 10
而不是 C 式“变量盒子里直接存一个固定宽度数值”。
Python int 是不可变对象:
x = 10
x = x + 1
不是把 10 原地改成 11,而是计算结果后让 x 重新绑定。
3.5 为什么整数不可变
本次对话中讨论了三个主要动机:
- 多个名字共享同一个整数对象时,不会产生原地修改带来的副作用;
- 整数适合稳定哈希,可作为
dict/setkey; - 不可变对象可以安全复用与缓存。
数学直觉也一致:数字 10 本身不会“变成 11”,只是变量从表示 10 改为表示 11。
3.6 Python int 是任意精度整数
Python int 不固定为 32/64 bit:
x = 10 ** 1000
仍可以表示,只要实现与内存资源允许。
CPython 底层整数类型使用 PyLongObject。
3.7 CPython 中的大整数表示
对话中以常见 CPython 构建为例,大整数拆成多个 digit/limb。概念公式:
常见配置中 B 可对应 2^30,因此可写作:
概念结构:
PyLongObject
├── Python 对象头
├── 符号 / digit 数量等信息
└── digits[]
├── d0
├── d1
└── ...
[!WARNING]
PyLongObject的精确字段、digit 位宽和标志布局是 CPython 版本/构建细节,不是 Python 语言规范。
3.8 小整数缓存
CPython 会复用一批高频小整数对象。对话中以 CPython 3.14.6 源码为例讨论了这一机制。
目的不是“不占内存”,而是:
少量固定内存
↓ 换取
减少频繁的小整数对象分配、初始化、回收
不可变性使共享安全。
4. 直观理解
Python 的核心模型可简化成:
名字
↓ 绑定
对象
↓
拥有类型和属性
对于整数:
原来:
x → 10
执行 x = x + 1
结果:
10
x → 11
变化的是 x 的绑定,不是 10 的值。
5. 工作原理
5.1 原整数对象如何处理
一般语义:
旧对象
│
├── 还有引用 → 继续存在
└── 没有引用 → 实现可以回收
例如:
x = 1000
y = x
x = x + 1
之后:
y → 1000
x → 1001
对于 CPython 缓存/immortal 的小整数,旧对象可能仍被 runtime 长期保留。
5.2 CPython 执行整数运算的概念链
Python 源码
↓
Python bytecode
↓
CPython interpreter
↓
整数操作实现
↓
Objects/longobject.c 等已编译 C 代码
↓
PyLongObject 结果
运行程序时不会重新读取或编译 longobject.c;它早已在构建 CPython 时编译成机器代码。
5.3 安装 Python 后源码在哪里
安装后的 Python 通常包括:
Python installation
├── python / python3 二进制可执行文件
├── 标准库纯 Python 文件
├── 编译扩展模块
├── site-packages
└── 其他运行时文件
完整 CPython C 源码并不要求随普通安装保留,研究实现时应查看 CPython 源码仓库。
本次对话提到的关键源码路径:
Include/object.h
Include/cpython/longintrepr.h
Objects/longobject.c
Include/internal/pycore_long.h
Include/internal/pycore_runtime_structs.h
Python/bytecodes.c
6. 示例
示例 1:绑定而不是修改
x = 10
y = x
x = 20
print(y) # 10
说明:x = 20 没有修改 10 对象。
示例 2:任意精度
x = 2 ** 1000
print(x.bit_length())
说明:语言层的整数宽度不受普通 CPU 寄存器宽度直接限制。
示例 3:整数方法
(10).bit_length()
(13).bit_count()
(1024).to_bytes(2, byteorder="big")
int.from_bytes(b"\x04\x00", byteorder="big")
这把 int 与二进制位、字节序联系起来。
7. 代码、命令或公式
整数不同字面量:
10
0b1010
0o12
0xA
它们表示相同整数值。
可用于观察对象:
import sys
x = 10
print(type(x))
print(id(x))
print(sys.getsizeof(x))
print(dir(x))
[!WARNING]
id()、sys.getsizeof()和 small-int identity 现象都不能直接上升为跨 Python 实现的语言规范。
8. 容易混淆的概念
| 概念 A | 概念 B | 核心区别 |
|---|---|---|
| 名字 | 对象 | 名字绑定对象,不等于对象本身 |
int | 10 | 前者是类型对象,后者是实例 |
| 修改名字绑定 | 修改不可变对象 | x = 11 是重新绑定 |
Python int | C int | Python 任意精度,C 常见为固定宽度 |
| function | bound method | bound method 额外绑定了实例 |
| small-int cache | Python 语言语义 | 前者是 CPython 实现优化 |
| C 源码 | 已安装解释器 | 源码用于构建,运行时执行的是编译产物 |
9. 常见误区
误区:函数只是“代码”,不算对象
错误原因:把可执行代码和承载它的运行时 function object 混为一谈。
正确理解:函数在 Python 中可以被赋值、传递、拥有属性。
如何验证:type(f)、f.__name__、把函数放进 list。
误区:x += 1 把整数对象 10 变成 11
错误原因:套用可变对象或 C 变量槽模型。
正确理解:int 不可变,名字重新绑定到结果对象。
如何验证:让 x、y 先绑定同一个整数,只更新 x。
误区:小整数缓存不占内存
错误原因:把“复用”理解成“不存储”。
正确理解:缓存本身占固定内存,目的是减少反复创建/销毁成本。
误区:Python 大整数由 CPU 无限宽整数寄存器直接支持
错误原因:把语言抽象能力等同硬件能力。
正确理解:CPython 用多个 digit 软件实现大整数算术。
10. 与其他知识的关系
11. 可以亲手完成的验证
实验目标:观察绑定、对象大小和方法属性。
import sys
x = 10
y = x
print(id(x), id(y))
x = 10 ** 1000
print(sys.getsizeof(x))
print(x.bit_length())
def f():
pass
print(type(f))
print(f.__name__)
预期:整数值越大,对象所需存储可能增加;函数拥有自己的类型和属性。
实验不能证明:CPython 所有版本都使用相同 small-int 范围或同样的 PyLongObject 字段布局。
12. 尚未解决的问题
PyObject_HEAD精确布局尚未逐字段展开。- descriptor 如何把类上的函数变成 bound method 尚未深入。
- CPython 引用计数、immortal object 与垃圾回收的完整关系尚未整理。
- 大整数乘法、除法采用哪些算法及规模阈值未讨论。
- CPython 不同平台安装目录结构尚未分别整理。
13. 自测问题
- “Everything is object” 为什么不意味着
if、+也是普通对象? p.hello为什么能自动得到self?x = 10中,x与10分别是什么?- 为什么
int设计为 immutable? - Python
int如何超过 CPU 原生整数宽度? - CPython 为什么缓存小整数,而不缓存所有整数?
- 为什么安装 Python 后通常看不到
longobject.c?
参考答案
- Everything is object 描述运行时值模型,关键字和运算语法属于语言语法层。
- 实例属性访问会产生绑定方法,保存实例与原函数的关联。
x是名字,10是int实例对象。- 可避免共享对象被意外修改、保持哈希稳定并允许安全缓存复用。
- CPython 把大整数拆成多个内部 digit/limb,用软件算法计算。
- 小整数高频且范围有限;所有整数集合无界,无法全部缓存。
- C 源码已在 CPython 构建阶段编译,普通安装主要提供运行构建产物。
14. 一句话总结
Python 通过统一对象模型把整数、函数、类和模块都纳入“对象—类型—属性”体系;int 再以不可变、任意精度语义存在,而 CPython 用 PyLongObject、多 digit 算术和小整数复用实现这套抽象。