Python 对象模型与属性访问机制

1. 引言

Python 的灵活性不仅体现在语法层面,更来源于其高度动态的对象模型与统一的属性访问机制。相比于仅停留在"如何使用类与对象",深入理解其底层运行原理,对于掌握 Django、ORM 乃至框架设计具有重要意义。

本文将围绕两个核心问题展开:

  • Python 对象是如何创建的?
  • Python 属性是如何被访问和控制的?

2. Python 对象模型

  1. 类的本质

    在 Python 中,类并不是一种特殊结构,而是一个普通对象,其创建由内置元类 type 完成。

    python 复制代码
    class A:
        x = 10

    等价于:

    python 复制代码
    A = type("A", (), {"x": 10})

    由此可以得到:

    • 类是 type 的实例
    • type 本身也是 type 的实例(自举结构)
    python 复制代码
    type(A) is type
    type(type) is type

    这一机制使 Python 具备极强的动态构造能力。

  2. 对象创建流程

    对象的创建本质上是对"类对象"的一次调用:

    python 复制代码
    a = A()

    在 Python 内部,该过程并非简单实例化,而是经历如下调用链:

    text 复制代码
    A()
    ↓
    type.__call__(A)
    ↓
    1️⃣ 调用 A.__new__()
       → 创建实例对象
    ↓
    2️⃣ 调用 A.__init__()
       → 初始化实例
    ↓
    返回实例

    需要注意:

    • 类之所以可以被调用,是因为其元类 type 实现了 __call__
    • __new__ 负责创建对象
    • __init__ 负责初始化对象

    在一般开发中无需直接操作 __call__,但该机制在元类与框架设计中具有重要作用。

  3. 属性存储机制

    Python 使用字典结构管理对象属性:

    • 实例属性:obj.__dict__
    • 类属性:class.__dict__

    示例:

    python 复制代码
    class A:
        x = 10
    
    a = A()

    此时:

    python 复制代码
    a.__dict__   # {}
    A.__dict__   # 包含 'x': 10

    当执行:

    python 复制代码
    a.x = 20

    本质为:

    python 复制代码
    a.__dict__['x'] = 20
  4. 属性查找规则

    访问属性:

    python 复制代码
    a.x

    默认查找顺序为:

    text 复制代码
    1️⃣ 实例字典 obj.__dict__
    2️⃣ 类字典 class.__dict__
    3️⃣ 父类链(MRO)

    示例:

    python 复制代码
    class A:
        x = 10
    
    a = A()
    a.x = 20

    优先返回实例属性 20。

    若执行:

    python 复制代码
    del a.x

    则回退至类属性,返回 10。

3. 属性访问机制

  1. 统一入口:getattribute

    在 Python 中,所有属性访问都会统一转化为:

    python 复制代码
    a.x  →  a.__getattribute__("x")

    因此可以认为:

    __getattribute__ 是属性访问的总入口

    示例:

    python 复制代码
    class A:
        def __getattribute__(self, name):
            print("访问", name)
            return super().__getattribute__(name)
  2. 兜底机制:getattr

    当属性在正常查找流程中未找到时,Python 会触发:

    python 复制代码
    __getattr__(self, name)

    示例:

    python 复制代码
    class A:
        def __getattr__(self, name):
            return 999
    
    a = A()
    a.x  # 返回 999
  3. 属性访问完整流程

    属性访问的完整流程如下:

    text 复制代码
    a.x
    ↓
    1️⃣ 调用 __getattribute__("x")(必定执行)
    ↓
    2️⃣ 若查找成功 → 直接返回
    ↓
    3️⃣ 若抛出 AttributeError
    ↓
    4️⃣ 调用 __getattr__("x")

    关键区别如下:

    方法 执行时机 作用
    __getattribute__ 必定执行 控制所有属性访问
    __getattr__ 查找失败后 提供兜底逻辑
  4. 异常驱动机制

    需要特别强调:

    __getattr__ 的触发依赖于 AttributeError

    例如:

    python 复制代码
    def __getattribute__(self, name):
        return 100

    此时不会触发 __getattr__,因为属性访问并未失败。

  5. 方法访问的本质

    在 Python 中,方法本质上也是属性:

    python 复制代码
    self.__getattr__

    等价于:

    python 复制代码
    self.__getattribute__("__getattr__")

    因此,在调试或日志中可能会看到:

    text 复制代码
    访问 __getattr__
  6. 递归风险与正确写法

    在重写 __getattribute__ 时,如果直接访问属性,容易导致无限递归:

    错误示例:

    python 复制代码
    def __getattribute__(self, name):
        return self.x

    其执行过程为:

    text 复制代码
    self.x → __getattribute__("x") → self.x → ...

    最终触发 RecursionError

    正确写法应为:

    python 复制代码
    return super().__getattribute__(name)
  7. 调试环境的特殊行为

    在调试工具(如 PyCharm Debug)中,可能出现如下输出:

    text 复制代码
    访问 __class__
    访问 __class__
    ...

    原因在于调试器会主动访问对象属性以获取类型与结构信息,例如:

    python 复制代码
    obj.__class__

    这些访问同样会触发 __getattribute__

    需要明确:

    这些行为来源于调试工具,而非程序本身逻辑。

4. 整体模型总结

  1. 对象关系结构

    text 复制代码
    type → 类 → 实例
    • type 负责创建类
    • 类负责创建实例
  2. 属性访问模型

    text 复制代码
    a.x
    ↓
    __getattribute__
    ↓
    (内部查找)
        obj.__dict__
        class.__dict__
        父类链
    ↓
    查找失败 → __getattr__

5. 工程意义

上述机制在实际工程中具有直接应用价值:

机制 应用场景
__getattribute__ 日志系统、权限控制
__getattr__ 安全访问、默认值处理
属性查找链 ORM 字段解析
type / 元类 模型定义、自动注册

例如:

  • Django ORM 的字段访问机制
  • DRF 的序列化流程
  • 权限控制与中间件设计

这些框架能力,本质上均建立在 Python 对象模型之上。

6. 总结

Python 的核心机制可以归纳为两部分:

text 复制代码
对象如何被创建(type / __new__ / __init__)
属性如何被访问(__getattribute__ / __getattr__)

进一步抽象为:

text 复制代码
Python = 对象模型 + 属性访问控制机制

理解这一点,意味着从"使用 Python"迈向"理解 Python",也是深入框架源码与系统设计的关键一步。

相关推荐
叼烟扛炮几秒前
C++ 知识点02 输入输出
开发语言·c++·算法·输入输出
qcx233 分钟前
深度解析Deepseek V4:1M 上下文不是军备竞赛,是养 Agent 的人才知道的痛
java·开发语言
小此方6 分钟前
Re:思考·重建·记录 现代C++ C++11篇(六) 从 shared_ptr 到 weak_ptr:起底智能指针的引用计数与循环引用之痛
开发语言·c++·c++11·现代c++
晨非辰8 分钟前
吃透C++两大默认成员函数:const成员函数、 & 取地址运算符重载
java·大数据·开发语言·c++·人工智能·后端·面试
啦啦啦_99999 分钟前
3. 欠拟合 & 正好拟合 & 过拟合
python
WL_Aurora10 分钟前
备战蓝桥杯国赛【Day 4】
python·蓝桥杯
我滴老baby11 分钟前
多智能体协作系统设计当AI学会团队合作效率翻十倍
android·开发语言·人工智能
落雪寒窗-12 分钟前
Python进阶核心路线(工程向)
开发语言·python
humcomm13 分钟前
全栈开发技术栈的最新进展(2026年视角)
开发语言·架构
yexuhgu29 分钟前
JavaScript中函数防抖Debounce的原理与闭包实现方案
jvm·数据库·python