Day 002 — 图解:变量与数据类型的底层原理
一图胜千言:用图示理解 Python 的变量模型
图 1:变量 = 标签,不是盒子
┌─────────────────────────────────────────────────────────────┐
│ Python 内存模型 │
│ │
│ 变量名(在命名空间中) 对象(在堆内存中) │
│ │
│ name ──────────────────► ┌──────────┐ │
│ │ "Alice" │ │
│ │ str 对象 │ │
│ age ──────────────────► │ 地址: │ │
│ │ 0x1000 │ │
│ └──────────┘ │
│ │
│ ┌──────────┐ │
│ height ──────────────────► │ 175.5 │ │
│ │ float对象 │ │
│ │ 地址: │ │
│ │ 0x2000 │ │
│ └──────────┘ │
│ │
│ is_student ──────────────► ┌──────────┐ │
│ │ False │ │
│ │ bool对象 │ │
│ │ 地址: │ │
│ │ 0x3000 │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
关键洞察:变量名和对象是完全分离的两件事。变量名只是一个"指针"或"标签",
指向堆内存中的真实对象。赋值 y = x 不会拷贝对象,只是多贴一个标签。
图 2:动态类型 vs 静态类型
┌─────────────────────────────────────────────────────────────┐
│ 静态类型(Java/C++) 动态类型(Python) │
│ │
│ int x = 10; x = 10 │
│ ┌──────┐ ┌──────┐ │
│ │ int │ │ 10 │ │
│ │ [10] │ 类型固定 │ int │ │
│ └──────┘ └──────┘ │
│ │
│ x = "hi"; // ❌ 编译错误 x = "hi" // ✅ 合法 │
│ ┌──────┐ │
│ │ "hi" │ │
│ │ str │ 类型可以变 │
│ └──────┘ │
│ │
│ 优点:编译期发现错误 优点:更灵活、开发更快 │
│ 缺点:不够灵活 缺点:运行时可能出错 │
└─────────────────────────────────────────────────────────────┘
图 3:小整数驻留(Interning)
┌─────────────────────────────────────────────────────────────┐
│ CPython 小整数缓存池(-5 ~ 256) │
│ │
│ 启动时预创建: │
│ │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │
│ │ -5 │ -4 │ -3 │ -2 │ -1 │ 0 │ 1 │ 2 │ ...│ 255│ 256│ │
│ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ │
│ ↑ ↑ ↑ ↑ │
│ │ │ │ │ │
│ ───┘ └─── 所有 = -4 的变量都指向同一个对象 ──┘ └─── │
│ │
│ a = 256 │
│ b = 256 │
│ a is b → True ✅ (指向同一个缓存对象) │
│ │
│ c = 257 │
│ d = 257 │
│ c is d → 可能是 False (每个创建了新对象) │
│ │
│ ⚠️ 永远用 == 比较数值,不要用 is │
└─────────────────────────────────────────────────────────────┘
图 4:Python 数据类型层级结构
classDiagram
class object {
+__class__
+__id__
}
class NoneType {
+ None(单例)
}
class bool {
+ True
+ False
}
class int {
+ 任意精度
+ 小整数缓存
}
class float {
+ IEEE 754
+ 精度≈15-17位
}
class str {
+ 不可变
+ Unicode
+ 字符串驻留
}
object <|-- NoneType
object <|-- int
object <|-- float
object <|-- str
int <|-- bool
classDiagram
class object {
+__class__
+__id__
}
class NoneType {
+ None(单例)
}
class bool {
+ True
+ False
}
class int {
+ 任意精度
+ 小整数缓存
}
class float {
+ IEEE 754
+ 精度≈15-17位
}
class str {
+ 不可变
+ Unicode
+ 字符串驻留
}
object <|-- NoneType
object <|-- int
object <|-- float
object <|-- str
int <|-- bool
注意:
bool继承自int,所以True本质上就是1,False就是0。
图 5:赋值语句的完整执行流程
sequenceDiagram
participant Source as 源代码: x = 42
participant Parser as Python 解析器
participant Compiler as 字节码编译器
participant VM as CPython 虚拟机
participant Memory as 堆内存
Source->>Parser: 解析源代码
Parser->>Compiler: AST → 字节码
Compiler->>VM: LOAD_CONST 42<br/>STORE_FAST x
Note over VM: 执行 LOAD_CONST
VM->>Memory: 检查 42 是否在缓存池中?
alt 是 (42在-5~256之间)
Memory-->>VM: 返回缓存池中已有的 42 对象地址
else 否
Memory->>Memory: 在堆上创建新的 int 对象
Memory-->>VM: 返回新对象的地址
end
Note over VM: 执行 STORE_FAST
VM->>VM: 将变量名 'x' 绑定到 42 对象地址
VM-->>Source: 执行完成 ✅
sequenceDiagram
participant Source as 源代码: x = 42
participant Parser as Python 解析器
participant Compiler as 字节码编译器
participant VM as CPython 虚拟机
participant Memory as 堆内存
Source->>Parser: 解析源代码
Parser->>Compiler: AST → 字节码
Compiler->>VM: LOAD_CONST 42<br/>STORE_FAST x
Note over VM: 执行 LOAD_CONST
VM->>Memory: 检查 42 是否在缓存池中?
alt 是 (42在-5~256之间)
Memory-->>VM: 返回缓存池中已有的 42 对象地址
else 否
Memory->>Memory: 在堆上创建新的 int 对象
Memory-->>VM: 返回新对象的地址
end
Note over VM: 执行 STORE_FAST
VM->>VM: 将变量名 'x' 绑定到 42 对象地址
VM-->>Source: 执行完成 ✅
图 6:Python 的"标签"赋值 vs 其他语言的"盒子"赋值
┌──────────────────────────────────────────────────────────────┐
│ Python(标签模型) C 语言(盒子模型) │
│ │
│ 初始: 初始: │
│ a = [1,2,3] int a[] = {1,2,3}; │
│ b = a int b[] = a; // 编译错误! │
│ 在 C 中数组不能直接赋值! │
│ 结果: 正确做法: │
│ ┌─────┐ int *b = a; // 指针 │
│ │ a───┼──┐ ┌─────┐ │
│ └─────┘ │ a 和 b 共享对象 │ a───┼──┐ │
│ ┌─────┐ ├──► [1,2,3] │ b───┼──┤→ [1,2,3] │
│ │ b───┼──┘ └─────┘ │ │
│ └─────┘ │ │
│ │
│ 后续: 后续变化类似,但需要手动管理 │
│ a.append(4) 所以 Python 更安全、更简洁 │
│ b → [1,2,3,4] ✅ │
└──────────────────────────────────────────────────────────────┘
图 7:浮点数精度问题图解
┌──────────────────────────────────────────────────────────────┐
│ 十进制 0.1 的二进制表示 │
│ │
│ 0.1 = 1/16 + 1/32 + 1/256 + 1/512 + 1/4096 + ...(无限循环)│
│ = 0.0001100110011001100110011001100110011... │
│ │
│ 在 IEEE 754 双精度浮点数中,只能用 52 位尾数存储: │
│ │
│ 尾数:0001100110011001100110011001100110011001100110011010 │
│ └────────────────── 52 bits ────────────────────────┘│
│ │
│ 截断后,实际存储值 ≈ 0.1000000000000000055511151231257827 │
│ │
│ 因此 0.1 + 0.2 ≠ 0.3 │
│ 实际结果 ≈ 0.30000000000000004 │
│ │
│ ✅ 建议解决方案: │
│ • 使用 math.isclose() 比大小 │
│ • 高精度场景使用 decimal.Decimal │
│ • 金融计算使用 Decimal 或整数分 │
└──────────────────────────────────────────────────────────────┘
🖊️ 这些图表帮助你直观理解 Python 变量机制的底层原理。 在阅读 README.md 之前先看这些图,可以建立更好的心智模型。