Python中的属性拦截与属性管理(`__getattr__`、`__getattribute__`、`__setattr__`、`__delattr__`)深入解析
字数 919 2025-11-08 10:03:28

Python中的属性拦截与属性管理(__getattr____getattribute____setattr____delattr__)深入解析

属性拦截是Python面向对象编程中的高级特性,允许你在访问、设置或删除对象属性时插入自定义逻辑。我们将通过一个实际场景来理解这些方法的区别、执行顺序和常见陷阱。

场景设定:创建一个智能字典类,它能记录属性访问日志,并对不存在的属性返回默认值而不是抛出异常。

1. 基础方法区分

  • __getattr__(self, name):仅当正常属性查找失败时调用(即实例字典、类、父类中均不存在该属性)
  • __getattribute__(self, name)无条件调用所有属性访问的入口(包括特殊方法)
  • __setattr__(self, name, value):拦截所有属性赋值操作
  • __delattr__(self, name):拦截所有属性删除操作

2. 实现基础日志功能

class LoggingDict:
    def __getattr__(self, name):
        print(f"__getattr__被调用,属性名: {name}")
        return f"默认值_{name}"  # 给不存在的属性返回默认值

    def __setattr__(self, name, value):
        print(f"__setattr__被调用: {name} = {value}")
        super().__setattr__(name, value)  # 必须调用父类避免递归

d = LoggingDict()
d.existing = "实际值"
print(d.existing)    # 正常访问,不会触发__getattr__
print(d.non_existing) # 触发__getattr__

输出结果:

__setattr__被调用: existing = 实际值
实际值
__getattr__被调用,属性名: non_existing
默认值_non_existing

3. __getattribute__的深度介入
现在添加__getattribute__并观察执行顺序:

class VerboseLoggingDict(LoggingDict):
    def __getattribute__(self, name):
        print(f"__getattribute__被调用: {name}")
        return super().__getattribute__(name)  # 必须通过父类继续查找链

v = VerboseLoggingDict()
v.test = "测试值"
print("--- 访问存在的属性 ---")
print(v.test)
print("--- 访问不存在的属性 ---")
print(v.unknown)

输出结果:

__getattribute__被调用: __dict__
__setattr__被调用: test = 测试值
--- 访问存在的属性 ---
__getattribute__被调用: test
测试值
--- 访问不存在的属性 ---
__getattribute__被调用: unknown
__getattr__被调用,属性名: unknown
默认值_unknown

4. 关键陷阱:避免递归
__getattribute____setattr__中直接访问属性会引发无限递归:

class BrokenDict:
    def __getattribute__(self, name):
        return self.name  # 错误!再次触发__getattribute__

# 正确做法:始终通过super()或直接操作__dict__
class CorrectDict:
    def __getattribute__(self, name):
        # 方法1:通过父类访问
        return super().__getattribute__(name)
        
    def __setattr__(self, name, value):
        # 方法2:直接操作对象字典
        self.__dict__[name] = value

5. 实际应用:惰性属性加载
结合这些方法实现首次访问时计算的属性:

class LazyProperty:
    def __init__(self):
        self._cache = {}

    def __getattr__(self, name):
        if name not in self._cache:
            print(f"计算属性 {name} 的值")
            self._cache[name] = f"{name}_计算结果"
        return self._cache[name]

lazy = LazyProperty()
print(lazy.data)  # 第一次访问触发计算
print(lazy.data)  # 直接从缓存返回

6. 方法调用顺序总结

  1. 访问obj.attr时,首先调用__getattribute__
  2. 如果属性不存在,__getattribute__抛出AttributeError
  3. 解释器捕获错误后尝试调用__getattr__
  4. __getattr__未定义或再次抛出异常,则向用户抛出AttributeError

关键要点

  • __getattribute__总在__getattr__之前调用
  • __getattribute__中避免直接访问实例属性
  • 通过super()object.__getattribute__()打破递归链
  • __getattr__更适合实现"后备属性"或虚拟属性
Python中的属性拦截与属性管理( __getattr__ 、 __getattribute__ 、 __setattr__ 、 __delattr__ )深入解析 属性拦截是Python面向对象编程中的高级特性,允许你在访问、设置或删除对象属性时插入自定义逻辑。我们将通过一个实际场景来理解这些方法的区别、执行顺序和常见陷阱。 场景设定 :创建一个智能字典类,它能记录属性访问日志,并对不存在的属性返回默认值而不是抛出异常。 1. 基础方法区分 __getattr__(self, name) :仅当 正常属性查找失败 时调用(即实例字典、类、父类中均不存在该属性) __getattribute__(self, name) : 无条件调用 所有属性访问的入口(包括特殊方法) __setattr__(self, name, value) :拦截 所有属性赋值 操作 __delattr__(self, name) :拦截 所有属性删除 操作 2. 实现基础日志功能 输出结果: 3. __getattribute__ 的深度介入 现在添加 __getattribute__ 并观察执行顺序: 输出结果: 4. 关键陷阱:避免递归 在 __getattribute__ 或 __setattr__ 中直接访问属性会引发无限递归: 5. 实际应用:惰性属性加载 结合这些方法实现首次访问时计算的属性: 6. 方法调用顺序总结 访问 obj.attr 时,首先调用 __getattribute__ 如果属性不存在, __getattribute__ 抛出 AttributeError 解释器捕获错误后尝试调用 __getattr__ 若 __getattr__ 未定义或再次抛出异常,则向用户抛出 AttributeError 关键要点 : __getattribute__ 总在 __getattr__ 之前调用 在 __getattribute__ 中避免直接访问实例属性 通过 super() 或 object.__getattribute__() 打破递归链 __getattr__ 更适合实现"后备属性"或虚拟属性