1. 引言

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

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

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

2. Python 对象模型

  1. 类的本质

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

    class A:
        x = 10
    

    等价于:

    A = type("A", (), {"x": 10})
    

    由此可以得到:

    • 类是 type 的实例
    • type 本身也是 type 的实例(自举结构)
    type(A) is type
    type(type) is type
    

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

  2. 对象创建流程

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

    a = A()
    

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

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

    需要注意:

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

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

  3. 属性存储机制

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

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

    示例:

    class A:
        x = 10
    
    a = A()
    

    此时:

    a.__dict__   # {}
    A.__dict__   # 包含 'x': 10
    

    当执行:

    a.x = 20
    

    本质为:

    a.__dict__['x'] = 20
    
  4. 属性查找规则

    访问属性:

    a.x
    

    默认查找顺序为:

    1️⃣ 实例字典 obj.__dict__
    2️⃣ 类字典 class.__dict__
    3️⃣ 父类链(MRO)
    

    示例:

    class A:
        x = 10
    
    a = A()
    a.x = 20
    

    优先返回实例属性 20。

    若执行:

    del a.x
    

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

3. 属性访问机制

  1. 统一入口:getattribute

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

    a.x  →  a.__getattribute__("x")
    

    因此可以认为:

    __getattribute__ 是属性访问的总入口

    示例:

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

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

    __getattr__(self, name)
    

    示例:

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

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

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

    关键区别如下:

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

    需要特别强调:

    __getattr__ 的触发依赖于 AttributeError

    例如:

    def __getattribute__(self, name):
        return 100
    

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

  5. 方法访问的本质

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

    self.__getattr__
    

    等价于:

    self.__getattribute__("__getattr__")
    

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

    访问 __getattr__
    
  6. 递归风险与正确写法

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

    错误示例:

    def __getattribute__(self, name):
        return self.x
    

    其执行过程为:

    self.x → __getattribute__("x") → self.x → ...
    

    最终触发 RecursionError

    正确写法应为:

    return super().__getattribute__(name)
    
  7. 调试环境的特殊行为

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

    访问 __class__
    访问 __class__
    ...
    

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

    obj.__class__
    

    这些访问同样会触发 __getattribute__

    需要明确:

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

4. 整体模型总结

  1. 对象关系结构

    type → 类 → 实例
    
    • type 负责创建类
    • 类负责创建实例
  2. 属性访问模型

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

5. 工程意义

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

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

例如:

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

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

6. 总结

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

对象如何被创建(type / __new__ / __init__)
属性如何被访问(__getattribute__ / __getattr__)

进一步抽象为:

Python = 对象模型 + 属性访问控制机制

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐