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

相关推荐
m0_613856291 小时前
mysql如何利用事务隔离级别解决特定业务冲突_mysql隔离方案选型
jvm·数据库·python
叶小鸡1 小时前
Java 篇-项目实战-苍穹外卖-笔记汇总
java·开发语言·笔记
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第22题:HashMap 和 HashSet 有哪些区别
java·开发语言·哈希算法·散列表·hash
我的xiaodoujiao2 小时前
API 接口自动化测试详细图文教程学习系列16--项目实战演练3
python·学习·测试工具·pytest
ID_180079054732 小时前
Python 实现亚马逊商品详情 API 数据准确性校验(极简可用 + JSON 参考)
java·python·json
时空系2 小时前
第10篇:继承扩展——面向对象编程进阶 python中文编程
开发语言·python·ai编程
CHANG_THE_WORLD3 小时前
python 批量终止进程exe
开发语言·python
古城小栈3 小时前
从 cargo-whero 库中,找到提升 rust 的契机
开发语言·后端·rust
liann1194 小时前
3.2_红队攻击框架--MITRE ATT&CK‌
python·网络协议·安全·网络安全·系统安全·信息与通信
云天AI实战派4 小时前
AI 智能体问题排查指南:ChatGPT、API 调用到 Agent 上线失灵的全流程修复手册
大数据·人工智能·python·chatgpt·aigc