Python 属性访问控制
属性访问控制通过魔术方法拦截属性的获取、设置和删除操作。
getattr:属性获取拦截
当访问不存在的属性时触发:
Python
class Dynamic:
def __getattr__(self, name):
print(f"获取不存在属性: {name}")
return f"动态属性 {name}"
d = Dynamic()
d.existing = "存在"
print(d.existing) # 存在(直接访问,不触发 __getattr__)
print(d.missing) # 获取不存在属性: missing → 动态属性 missing
惰性加载示例
Python
class LazyLoader:
def __init__(self):
self._loaded = {}
def __getattr__(self, name):
if name.startswith('get_'):
key = name[4:]
if key not in self._loaded:
print(f"加载 {key}...")
self._loaded[key] = f"{key} 的数据"
return lambda: self._loaded[key]
raise AttributeError(f"无属性 {name}")
loader = LazyLoader()
get_user = loader.get_user # 加载 user...
print(get_user()) # user 的数据
get_user = loader.get_user # 不再加载(已缓存)
getattribute:无条件拦截
拦截所有属性访问(包括存在的属性):
Python
class AccessLogger:
def __getattribute__(self, name):
print(f"访问属性: {name}")
return super().__getattribute__(name) # 必须用 super()
obj = AccessLogger()
obj.x = 10
print(obj.x) # 访问属性: x → 10
__getattribute__会拦截所有访问,必须用super()获取真实属性,否则会无限递归。
getattr vs getattribute
| 方法 | 触发条件 |
|---|---|
__getattr__ | 属性不存在时 |
__getattribute__ | 所有属性访问 |
setattr:属性设置拦截
拦截所有属性赋值:
Python
class Validator:
def __setattr__(self, name, value):
if name == 'age' and not isinstance(value, int):
raise TypeError("age 必须是整数")
if name == 'age' and value < 0:
raise ValueError("age 不能为负")
# 存储到 __dict__ 避免递归
self.__dict__[name] = value
v = Validator()
v.age = 25 # 正常
v.age = -5 # ValueError
v.age = "25" # TypeError
v.name = "OK" # 正常
避免递归的方法
Python
class SafeSetAttr:
def __setattr__(self, name, value):
# 方法1:使用 __dict__
self.__dict__[name] = value
# 方法2:使用 super()
super().__setattr__(name, value)
# 错误:self.name = value 会导致无限递归
delattr:属性删除拦截
Python
class Protected:
def __init__(self):
self.protected = True
self.normal = "可删除"
def __delattr__(self, name):
if name == 'protected':
raise AttributeError("不能删除 protected")
super().__delattr__(name)
p = Protected()
del p.normal # 正常删除
del p.protected # AttributeError
完整示例:代理类
Python
class Proxy:
"代理类:拦截所有操作"
def __init__(self, target):
self._target = target
def __getattr__(self, name):
print(f"代理获取: {name}")
return getattr(self._target, name)
def __setattr__(self, name, value):
if name == '_target':
super().__setattr__(name, value)
else:
print(f"代理设置: {name} = {value}")
setattr(self._target, name, value)
def __delattr__(self, name):
print(f"代理删除: {name}")
delattr(self._target, name)
class RealObject:
def __init__(self):
self.data = "原始数据"
real = RealObject()
proxy = Proxy(real)
proxy.data # 代理获取: data
proxy.new_attr = 10 # 代理设置: new_attr = 10
del proxy.new_attr # 代理删除: new_attr
完整示例:只读属性
Python
class ReadOnly:
def __init__(self, data):
# 初始化时绕过 __setattr__
self.__dict__['_data'] = data
self.__dict__['_locked'] = True
def __setattr__(self, name, value):
if self.__dict__.get('_locked'):
raise AttributeError("对象已锁定,不可修改")
super().__setattr__(name, value)
def __delattr__(self, name):
if self.__dict__.get('_locked'):
raise AttributeError("对象已锁定,不可删除")
super().__delattr__(name)
obj = ReadOnly({"a": 1})
print(obj._data) # {"a": 1}
obj._data = {} # AttributeError
del obj._data # AttributeError
完整示例:类型检查
Python
class Typed:
"带类型检查的类"
_types = {
'name': str,
'age': int,
'score': (int, float)
}
def __setattr__(self, name, value):
if name in self._types:
expected = self._types[name]
if not isinstance(value, expected):
raise TypeError(f"{name} 必须是 {expected}")
super().__setattr__(name, value)
class Student(Typed):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
s = Student("Alice", 20, 95.5)
s.age = "twenty" # TypeError: age 必须是 int
s.extra = "OK" # 不检查不在 _types 中的属性
属性查找顺序
Python
1. __getattribute__(无条件拦截)
2. data descriptor(描述符 __get__)
3. 实例 __dict__
4. non-data descriptor(描述符 __get__)
5. 类 __dict__ 及继承链
6. __getattr__(不存在时触发)
与 property 的区别
| 方式 | 适用场景 | 灵活度 |
|---|---|---|
@property | 单个属性的精细控制 | 简单清晰 |
__getattr__ | 动态属性、代理 | 全局拦截 |
__setattr__ | 批量验证、类型检查 | 全局拦截 |
text
# property 方式:针对单个属性
class Person:
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("age 不能为负")
self._age = value
# __setattr__ 方式:全局拦截
class Person2:
def __setattr__(self, name, value):
if name == 'age' and value < 0:
raise ValueError("age 不能为负")
self.__dict__[name] = value
要点总结
| 方法 | 触发时机 | 注意事项 |
|---|---|---|
__getattr__ | 属性不存在 | 不拦截已存在属性 |
__getattribute__ | 所有访问 | 必须用 super() 防递归 |
__setattr__ | 所有赋值 | 存入 dict 防递归 |
__delattr__ | 所有删除 | 用 super() 删除 |
__setattr__和__getattribute__内部必须避免直接访问 self 属性,否则会无限递归。
D:\git2\jwdev\articles\PYTHON\进阶\面向对象编程\属性访问控制.md
📝 发现内容有误?点击此处直接编辑