Python中的属性拦截与属性管理(`__getattr__`、`__getattribute__`、`__setattr__`、`__delattr__`)实战案例
字数 1012 2025-11-08 10:03:28
Python中的属性拦截与属性管理(__getattr__、__getattribute__、__setattr__、__delattr__)实战案例
1. 问题描述
在Python中,我们可以通过重写特殊方法(如__getattr__、__getattribute__、__setattr__、__delattr__)来拦截对象的属性访问、设置和删除操作。这些方法常用于实现动态属性、属性验证、惰性加载或代理模式。但它们的调用时机和用法容易混淆,需要通过实际案例加深理解。
2. 核心方法区分
-
__getattr__(self, name)- 触发条件:当访问不存在的属性时触发。
- 常见用途:动态生成属性或实现降级策略(如默认值)。
-
__getattribute__(self, name)- 触发条件:访问任何属性时都会触发(包括存在的属性)。
- 风险:若实现不当易导致递归调用(如内部需用
super().__getattribute__)。
-
__setattr__(self, name, value)- 触发条件:设置属性时触发(包括初始化时的赋值)。
- 注意:内部赋值需避免直接
self.name = value(会递归),应使用super().__setattr__。
-
__delattr__(self, name)- 触发条件:删除属性时触发。
3. 实战案例:实现属性访问日志与自动类型验证
假设我们需要一个类,满足以下需求:
- 记录所有属性的访问、设置和删除日志。
- 对某些属性(如
age)进行类型验证(必须为整数)。
步骤1:基础类结构
class LoggedClass:
def __init__(self):
# 避免递归,通过__dict__直接赋值
self.__dict__["_attributes"] = {}
self.__dict__["_validations"] = {"age": int} # 需要类型验证的属性
def _log(self, action, name, value=None):
print(f"[LOG] {action} attribute '{name}': {value}")
步骤2:重写__setattr__实现类型验证与日志
def __setattr__(self, name, value):
# 类型验证逻辑
if name in self._validations:
if not isinstance(value, self._validations[name]):
raise TypeError(f"{name} must be {self._validations[name].__name__}")
# 记录日志
self._log("SET", name, value)
# 实际存储属性(避免递归)
super().__setattr__(name, value) # 或 self._attributes[name] = value
步骤3:重写__getattribute__记录属性访问
def __getattribute__(self, name):
# 先获取属性值(避免递归需用super)
value = super().__getattribute__(name)
# 记录访问日志(排除内置方法和日志方法本身)
if not name.startswith("_"):
self._log("GET", name, value)
return value
步骤4:重写__getattr__处理不存在的属性
def __getattr__(self, name):
# 动态返回默认值
self._log("GET_MISSING", name, "DEFAULT_NONE")
return None # 或自定义逻辑(如惰性加载)
步骤5:重写__delattr__记录删除操作
def __delattr__(self, name):
self._log("DEL", name)
super().__delattr__(name)
4. 完整代码与测试
class LoggedClass:
def __init__(self):
self.__dict__["_attributes"] = {}
self.__dict__["_validations"] = {"age": int}
def _log(self, action, name, value=None):
print(f"[LOG] {action} attribute '{name}': {value}")
def __setattr__(self, name, value):
if name in self._validations and not isinstance(value, self._validations[name]):
raise TypeError(f"{name} must be {self._validations[name].__name__}")
self._log("SET", name, value)
super().__setattr__(name, value)
def __getattribute__(self, name):
value = super().__getattribute__(name)
if not name.startswith("_"):
self._log("GET", name, value)
return value
def __getattr__(self, name):
self._log("GET_MISSING", name, "DEFAULT_NONE")
return None
def __delattr__(self, name):
self._log("DEL", name)
super().__delattr__(name)
# 测试
obj = LoggedClass()
obj.name = "Alice" # 触发SET日志
obj.age = 30 # 正常赋值
print(obj.name) # 触发GET日志
print(obj.salary) # 触发GET_MISSING日志
del obj.name # 触发DEL日志
5. 关键注意事项
- 避免递归:在
__getattribute__和__setattr__中,必须通过super()或直接操作__dict__来访问/设置属性。 - 性能影响:频繁拦截属性操作可能降低性能,需谨慎使用。
- 应用场景:动态代理(如ORM)、属性权限控制、调试工具等。
通过此案例,你可以掌握如何组合使用属性拦截方法,实现灵活的对象行为控制。