第 2 章:Python 对象模型
2.1 一切皆对象
原理讲解
在 Python 中,一切皆对象(Everything is an object):
- 数字、字符串、函数、类、模块... 都是对象
- 每个对象都有三个核心属性:
-
引用计数(reference count)
-
类型指针(type pointer)
-
对象内容(object content)
┌─────────────────────────────────────────────────────────┐
│ Python 对象模型 │
├─────────────────────────────────────────────────────────┤
│ │
│ int(42) str("hi") def func() class MyClass │
│ ↓ ↓ ↓ ↓ │
│ ┌───────────────────────────────────────────────┐ │
│ │ PyObject 基结构 │ │
│ │ ┌────────────┬────────────┬────────────┐ │ │
│ │ │ ob_refcnt │ ob_type │ ob_size │ │ │
│ │ │ (引用计数) │ (类型指针) │ (大小) │ │ │
│ │ └────────────┴────────────┴────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
-
PyObject 结构
在 CPython 源码中(Include/cpython/object.h):
c
// PyObject 基结构
typedef struct _object {
PyObject_HEAD
} PyObject;
// PyObject_HEAD 宏展开
#define PyObject_HEAD \
Py_ssize_t ob_refcnt; \
PyTypeObject *ob_type;
// 带大小的变长对象
#define PyObject_HEAD_INIT(type) \
{1, type},
#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size;
结构详解:
┌─────────────────────────────────────────┐
│ PyObject 内存布局 │
├─────────────────────────────────────────┤
│ 偏移 0: ob_refcnt (8 bytes) │
│ 引用计数,追踪有多少引用指向此对象 │
├─────────────────────────────────────────┤
│ 偏移 8: ob_type (8 bytes) │
│ 指向类型对象的指针 │
├─────────────────────────────────────────┤
│ 偏移 16: ob_size (8 bytes, 变长对象) │
│ 对象包含的元素数量 │
├─────────────────────────────────────────┤
│ 偏移 24+: 实际数据 │
│ 对象的具体内容 │
└─────────────────────────────────────────┘
引用计数 (ob_refcnt)
- 每次创建新引用时 +1
- 每次删除引用时 -1
- 归零时触发内存释放
python
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2 (a + getrefcount 的参数)
b = a
print(sys.getrefcount(a)) # 3 (a, b, 参数)
del b
print(sys.getrefcount(a)) # 2
类型指针 (ob_type)
- 指向对象的类型对象
- 决定对象支持的操作
- 实现动态类型系统
python
x = 42
print(type(x)) # <class 'int'>
print(x.__class__) # <class 'int'>
# 类型对象本身也是对象
print(type(type(x))) # <class 'type'>
2.2 类型与类
type 的本质
type 是 Python 的元类:
┌─────────────────────────────────────────────────────────┐
│ Python 类型层次 │
├─────────────────────────────────────────────────────────┤
│ │
│ object (基类) │
│ ↑ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ │ │
│ type (元类) int, str, list... │
│ ↑ ↑ │
│ │ │ │
│ MyClass my_instance │
│ (类对象) (实例对象) │
│ │
│ 关系: │
│ - MyClass 是 type 的实例:isinstance(MyClass, type) │
│ - my_instance 是 MyClass 的实例 │
│ - type 和 object 互相引用 │
│ │
└─────────────────────────────────────────────────────────┘
python
# 验证类型关系
class MyClass:
pass
obj = MyClass()
print(f"obj 的类型:{type(obj)}") # <class '__main__.MyClass'>
print(f"MyClass 的类型:{type(MyClass)}") # <class 'type'>
print(f"type 的类型:{type(type)}") # <class 'type'>
print(f"obj 的类:{obj.__class__}") # <class '__main__.MyClass'>
print(f"MyClass 的基类:{MyClass.__bases__}") # (<class 'object'>,)
元类 (metaclass) 简介
元类是创建类的类:
python
# 默认元类是 type
class MyClass:
pass
# 等价于
MyClass = type('MyClass', (), {})
# 自定义元类
class MyMeta(type):
def __new__(cls, name, bases, attrs):
print(f"创建类:{name}")
attrs['custom_attr'] = 'added by metaclass'
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=MyMeta):
pass
print(MyClass.custom_attr) # 'added by metaclass'
元类的使用场景:
- ORM 框架(自动注册模型)
- API 客户端(自动生成方法)
- 验证和约束(强制子类实现某些方法)
- 单例模式实现
class 和 dict
class :指向对象的类型
dict:存储对象的属性(实例字典)
python
class Person:
species = "Homo sapiens" # 类属性
def __init__(self, name):
self.name = name # 实例属性
p = Person("Alice")
# __class__ 指向类型
print(p.__class__) # <class '__main__.Person'>
print(p.__class__.species) # 'Homo sapiens'
# __dict__ 存储实例属性
print(p.__dict__) # {'name': 'Alice'}
# 类也有 __dict__
print(Person.__dict__.keys())
# dict_keys(['__module__', 'species', '__init__', ...])
属性查找顺序:
实例.__dict__ → 类.__dict__ → 父类.__dict__ → ... → object.__dict__
2.3 实践:用 C 扩展查看 PyObject 结构
Python 方式查看对象信息
python
# examples/chapter-02/inspect_object.py
import sys
import ctypes
def inspect_object(obj):
"""使用 ctypes 查看 PyObject 头部信息"""
# 获取对象地址
obj_addr = id(obj)
# PyObject 结构定义
class PyObject(ctypes.Structure):
_fields_ = [
("ob_refcnt", ctypes.c_ssize_t),
("ob_type", ctypes.c_void_p),
]
# 将地址转换为结构体指针
py_obj = ctypes.cast(obj_addr, ctypes.POINTER(PyObject)).contents
print(f"对象地址:{hex(obj_addr)}")
print(f"引用计数:{py_obj.ob_refcnt}")
print(f"类型指针:{hex(py_obj.ob_type)}")
print(f"类型名称:{type(obj).__name__}")
print(f"对象大小:{sys.getsizeof(obj)} bytes")
# 如果是变长对象,尝试读取 ob_size
class PyObjectVar(ctypes.Structure):
_fields_ = [
("ob_refcnt", ctypes.c_ssize_t),
("ob_type", ctypes.c_void_p),
("ob_size", ctypes.c_ssize_t),
]
try:
py_obj_var = ctypes.cast(obj_addr, ctypes.POINTER(PyObjectVar)).contents
print(f"对象大小 (ob_size): {py_obj_var.ob_size}")
except:
pass
# 测试不同类型
print("=== 整数对象 ===")
inspect_object(42)
print("\n=== 字符串对象 ===")
inspect_object("hello")
print("\n=== 列表对象 ===")
inspect_object([1, 2, 3])
print("\n=== 自定义对象 ===")
class MyClass:
pass
inspect_object(MyClass())
实验练习
练习 1:观察引用计数变化
python
import sys
def test_refcount():
# 创建对象
data = {"key": "value"}
initial_count = sys.getrefcount(data)
print(f"初始引用计数:{initial_count}")
# 添加引用
ref1 = data
ref2 = data
print(f"添加引用后:{sys.getrefcount(data)}")
# 删除引用
del ref1
print(f"删除 ref1 后:{sys.getrefcount(data)}")
# 函数中的引用
def use_data(d):
print(f"函数内引用计数:{sys.getrefcount(d)}")
use_data(data)
print(f"函数调用后:{sys.getrefcount(data)}")
test_refcount()
练习 2:探索 dict 内存
python
class HeavyClass:
def __init__(self):
# 添加大量属性
for i in range(1000):
setattr(self, f'attr_{i}', i)
obj = HeavyClass()
print(f"对象大小:{sys.getsizeof(obj)}")
print(f"__dict__ 大小:{sys.getsizeof(obj.__dict__)}")
print(f"__dict__ 条目数:{len(obj.__dict__)}")
练习 3:比较不同对象的内存布局
python
import sys
objects = [
42, # 小整数
2**100, # 大整数
"", # 空字符串
"hello", # 短字符串
"x" * 1000, # 长字符串
[], # 空列表
[1, 2, 3], # 小列表
list(range(1000)), # 大列表
{}, # 空字典
{"a": 1, "b": 2}, # 小字典
]
for obj in objects:
print(f"{type(obj).__name__:10} | 大小:{sys.getsizeof(obj):6} | "
f"引用计数:{sys.getrefcount(obj):3}")
常见问题
Q1: 小整数为什么引用计数很高?
A: Python 缓存了 -5 到 256 的整数:
python
a = 1
b = 1
print(a is b) # True (同一个对象)
这些整数在解释器启动时创建,被多处引用。
Q2: slots 如何节省内存?
A : __slots__ 禁用 __dict__,使用固定结构:
python
class WithoutSlots:
def __init__(self):
self.x = 1 # 存储在 __dict__
class WithSlots:
__slots__ = ['x']
def __init__(self):
self.x = 1 # 存储在固定位置
每个实例节省约 100+ 字节。
Q3: 为什么 type 的 type 是 type 自己?
A: 这是自指结构:
python
print(type(type)) # <class 'type'>
type 是自己的实例,这是元类系统的基石。
Q4: 如何查看对象的完整内存布局?
A: 使用专门工具:
bash
# 安装 objgraph
pip install objgraph
# 或使用 memory_profiler
pip install memory_profiler
Q5: 循环引用如何影响引用计数?
A: 循环引用导致引用计数永不为零:
python
a = []
b = []
a.append(b) # a 引用 b
b.append(a) # b 引用 a
# 即使删除外部引用,内部引用仍存在
del a, b
# 需要 GC 来清理
本章小结
- Python 中一切皆对象,包括类型和函数
- PyObject 是对象的 C 结构基类,包含引用计数和类型指针
- 引用计数是主要内存管理机制,但有循环引用问题
- type 是元类,用于创建类对象
__dict__存储实例属性,__slots__可优化内存- 理解对象模型是理解 Python 的关键
下一章预告
第 3 章将深入探讨 内存管理,包括引用计数、垃圾回收和内存池机制。