【Python】第 2 章:Python 对象模型

第 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 客户端(自动生成方法)
  • 验证和约束(强制子类实现某些方法)
  • 单例模式实现

classdict

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 章将深入探讨 内存管理,包括引用计数、垃圾回收和内存池机制。

相关推荐
萝卜白菜。5 分钟前
TongWeb7.0相同的类指明加载顺序
开发语言·python·pycharm
wb043072015 分钟前
使用 Java 开发 MCP 服务并发布到 Maven 中央仓库完整指南
java·开发语言·spring boot·ai·maven
Rsun045517 分钟前
设计模式应该怎么学
java·开发语言·设计模式
赵钰老师14 分钟前
【ADCIRC】基于“python+”潮汐、风驱动循环、风暴潮等海洋水动力模拟实践技术应用
python·信息可视化·数据分析
良木生香24 分钟前
【C++初阶】:C++类和对象(下):构造函数promax & 类型转换 & static & 友元 & 内部类 & 匿名对象 & 超级优化
c语言·开发语言·c++
爬山算法28 分钟前
MongoDB(80)如何在MongoDB中使用多文档事务?
数据库·python·mongodb
5系暗夜孤魂29 分钟前
系统越复杂,越需要“边界感”:从 Java 体系理解大型工程的可维护性本质
java·开发语言
YuanDaima20481 小时前
基于 LangChain 1.0 的检索增强生成(RAG)实战
人工智能·笔记·python·langchain·个人开发·langgraph
无巧不成书02181 小时前
C语言零基础速通指南 | 1小时从入门到跑通完整项目
c语言·开发语言·编程实战·c语言入门·零基础编程·c语言速通
三雷科技1 小时前
使用 `dlopen` 动态加载 `.so` 文件
开发语言·c++·算法