一、属性访问方法概述
1. 四种核心方法
| | | |
|---|
__getattr__ | | | |
__getattribute__ | | | |
__setattr__ | | | |
__delattr__ | | | |
2. 方法调用顺序
属性访问(obj.name) ↓__getattribute__(总是调用) ↓如果属性存在 → 返回属性值如果属性不存在 → 调用 __getattr__ ↓如果 __getattr__ 不存在 → 抛出 AttributeError
二、__getattr__ 详解
1. 基本用法
class DynamicAttributes: def __init__(self): self.existing_attr = "我是存在的属性" self._data = {"name": "Alice", "age": 25} def __getattr__(self, name): """ 访问不存在的属性时调用 name: 要访问的属性名 应该返回属性值或抛出 AttributeError """ print(f"__getattr__ 被调用,访问不存在的属性: {name}") # 尝试从 _data 字典中获取 if name in self._data: return self._data[name] # 动态创建方法 if name.startswith('get_'): attr_name = name[4:] def getter(): return self._data.get(attr_name, "未知") return getter # 属性不存在,抛出异常 raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")# 使用obj = DynamicAttributes()print(obj.existing_attr) # 存在属性,不会调用 __getattr__print(obj.name) # 不存在属性,调用 __getattr__,返回 "Alice"print(obj.age) # 不存在属性,调用 __getattr__,返回 25# 动态方法get_name = obj.get_nameprint(get_name()) # 调用动态创建的方法,返回 "Alice"try: print(obj.nonexistent)except AttributeError as e: print(f"错误: {e}")
2. 实际应用:属性代理
class ConfigProxy: """配置文件代理 - 从配置文件动态加载属性""" def __init__(self, config_file=None): self.config_file = config_file self._loaded_config = {} def __getattr__(self, name): """从配置文件加载配置项""" print(f"尝试获取配置项: {name}") # 如果是特殊属性,交给默认处理 if name.startswith('_'): return super().__getattr__(name) # 尝试从已加载的配置中获取 if name in self._loaded_config: return self._loaded_config[name] # 模拟从文件加载配置 config_value = self._load_from_file(name) if config_value is not None: self._loaded_config[name] = config_value return config_value # 返回默认值 return None def _load_from_file(self, name): """模拟从配置文件加载""" # 这里应该是实际的文件读取逻辑 mock_config = { 'database_url': 'mysql://localhost:3306/mydb', 'debug_mode': True, 'max_connections': 100, 'timeout': 30 } return mock_config.get(name)# 使用config = ConfigProxy('config.ini')# 访问配置项(第一次会加载)print(config.database_url) # mysql://localhost:3306/mydbprint(config.debug_mode) # True# 第二次访问直接使用缓存的值print(config.database_url) # 直接从 _loaded_config 返回# 不存在的配置返回 Noneprint(config.nonexistent) # None
三、__getattribute__ 详解
1. 基本用法和注意事项
class AttributeInspector: def __init__(self): self.name = "Alice" self.age = 25 def __getattribute__(self, name): """ 访问任何属性时都会调用 注意:必须非常小心避免无限递归 """ print(f"正在访问属性: {name}") # 使用 super() 获取属性(避免递归) try: value = super().__getattribute__(name) except AttributeError: value = None print(f"属性 {name} 的值是: {value}") return value def __getattr__(self, name): """当属性不存在时调用""" return f"默认值({name})"# 使用obj = AttributeInspector()print(obj.name) # 访问存在的属性print(obj.age) # 访问存在的属性print(obj.city) # 访问不存在的属性
2. 避免无限递归
class SafeAttributeAccess: def __init__(self): self.data = {"a": 1, "b": 2} def __getattribute__(self, name): """安全的属性访问实现""" # 危险写法(会引起无限递归): # return self.__dict__[name] # 会再次调用 __getattribute__ # 正确写法1:使用 object.__getattribute__ # return object.__getattribute__(self, name) # 正确写法2:使用 super() if name == 'special': return "特殊处理" return super().__getattribute__(name) def __setattr__(self, name, value): """安全的属性设置""" # 危险写法: # self.name = value # 会再次调用 __setattr__ # 正确写法: super().__setattr__(name, value) def __getattr__(self, name): """访问不存在属性时的备用方案""" if name.startswith('get_'): key = name[4:] return lambda: self.data.get(key) return super().__getattr__(name)# 使用obj = SafeAttributeAccess()print(obj.a) # 不存在,但不会无限递归
四、__setattr__ 详解
1. 属性验证
class ValidatedPerson: def __init__(self, name, age, email): # 直接调用 __setattr__ 进行初始化 self.name = name self.age = age self.email = email def __setattr__(self, name, value): """设置属性时进行验证""" print(f"验证属性: {name} = {value}") if name == 'name': if not isinstance(value, str): raise TypeError("姓名必须是字符串") if len(value) < 2: raise ValueError("姓名长度至少为2个字符") elif name == 'age': if not isinstance(value, (int, float)): raise TypeError("年龄必须是数字") if not (0 <= value <= 150): raise ValueError("年龄必须在0-150之间") elif name == 'email': if not isinstance(value, str): raise TypeError("邮箱必须是字符串") if '@' not in value: raise ValueError("邮箱格式不正确") # 调用父类的 __setattr__ 实际设置属性 super().__setattr__(name, value)# 使用try: p = ValidatedPerson("A", 25, "test@example.com") # 姓名太短except ValueError as e: print(f"创建失败: {e}")try: p = ValidatedPerson("Alice", 200, "test@example.com") # 年龄太大except ValueError as e: print(f"创建失败: {e}")try: p = ValidatedPerson("Alice", 25, "invalid-email") # 邮箱无效except ValueError as e: print(f"创建失败: {e}")# 正确创建p = ValidatedPerson("Alice", 25, "alice@example.com")print(f"成功创建: {p.name}, {p.age}, {p.email}")
2. 属性类型转换和规范化
class NormalizedAttributes: def __setattr__(self, name, value): """设置属性时自动转换和规范化""" # 类型转换规则 type_rules = { 'age': int, 'price': float, 'name': str, 'tags': lambda x: x if isinstance(x, list) else [x], 'active': bool } # 名称规范化 name = name.lower().strip() # 应用类型转换 if name in type_rules: try: converter = type_rules[name] value = converter(value) except (ValueError, TypeError): pass # 字符串规范化 if isinstance(value, str): value = value.strip() if name in ('name', 'title', 'description'): value = value.capitalize() # 数字范围限制 if name == 'age': value = max(0, min(150, value)) elif name == 'price': value = max(0, value) # 设置属性 super().__setattr__(name, value)# 使用obj = NormalizedAttributes()# 自动转换类型obj.age = "25" # 自动转换为 intobj.price = "19.99" # 自动转换为 floatobj.active = 1 # 自动转换为 bool (True)obj.tags = "python" # 自动转换为 ["python"]print(f"age: {obj.age} ({type(obj.age).__name__})")print(f"price: {obj.price} ({type(obj.price).__name__})")print(f"active: {obj.active} ({type(obj.active).__name__})")print(f"tags: {obj.tags}")# 自动规范化obj.NAME = " alice " # 自动转小写并去除空格obj.title = "python programming" # 自动首字母大写print(f"name: {obj.name}")print(f"title: {obj.title}")# 范围限制obj.age = 200 # 自动限制为150obj.price = -10 # 自动限制为0print(f"age: {obj.age}")print(f"price: {obj.price}")
五、__delattr__ 详解
1. 基本用法和权限控制
class ProtectedObject: def __init__(self): self.public = "公开属性" self._protected = "受保护属性" self.__private = "私有属性" # 会被名称修饰 self._critical = "关键属性" self.deletable = "可删除属性" def __delattr__(self, name): """删除属性时的控制""" print(f"尝试删除属性: {name}") # 禁止删除某些属性 if name == '_critical': raise AttributeError(f"禁止删除关键属性 '{name}'") if name.startswith('_'): # 需要确认 confirm = input(f"确定要删除受保护属性 '{name}'? (y/n): ") if confirm.lower() != 'y': print("取消删除") return # 执行实际的删除操作 super().__delattr__(name) print(f"属性 '{name}' 已删除")# 使用obj = ProtectedObject()# 删除普通属性del obj.deletable# 尝试删除关键属性try: del obj._criticalexcept AttributeError as e: print(f"错误: {e}")# 删除受保护属性(需要确认)# del obj._protected # 会提示确认
2. 清理资源
import tempfileimport osclass TempFileManager: """临时文件管理器""" def __init__(self): self.temp_files = [] self.data = {} self._create_temp_files() def _create_temp_files(self): """创建临时文件""" for i in range(3): fd, path = tempfile.mkstemp() os.close(fd) self.temp_files.append(path) self.data[f"file{i}"] = path print(f"创建了 {len(self.temp_files)} 个临时文件") def __delattr__(self, name): """删除属性时清理相关资源""" print(f"清理属性 '{name}' 的资源") if name in self.data: # 删除对应的临时文件 file_path = self.data[name] if os.path.exists(file_path): os.remove(file_path) print(f"已删除临时文件: {file_path}") # 执行实际的删除操作 super().__delattr__(name) def cleanup_all(self): """清理所有资源""" for name in list(self.data.keys()): delattr(self, name) # 删除剩余的临时文件 for file_path in self.temp_files: if os.path.exists(file_path): os.remove(file_path) print("所有资源已清理")# 使用manager = TempFileManager()# 删除单个属性(自动清理文件)del manager.file0# 或者批量清理manager.cleanup_all()
六、__dir__ 详解
1. 基本用法
class CustomDir: def __init__(self): self.name = "Alice" self.age = 25 self._secret = "秘密" def __dir__(self): """控制 dir() 的输出""" print("__dir__ 被调用") # 获取默认的属性和方法列表 default_dir = super().__dir__() # 可以添加自定义的属性 custom_attributes = ['dynamic_attr1', 'dynamic_attr2'] # 也可以过滤掉某些属性 filtered = [attr for attr in default_dir if not attr.startswith('_') or attr == '__init__'] # 合并并返回 return sorted(set(filtered + custom_attributes)) def __getattr__(self, name): """支持动态属性""" if name.startswith('dynamic_'): return f"动态属性 {name} 的值"# 使用obj = CustomDir()print(dir(obj))print([attr for attr in dir(obj) if not attr.startswith('__')])
2. 实际应用:API文档生成
class APIClient: """API客户端 - 动态生成方法""" def __init__(self, base_url): self.base_url = base_url self._available_endpoints = { 'users': ['get', 'post', 'put', 'delete'], 'posts': ['get', 'post'], 'comments': ['get', 'post', 'put'] } def __getattr__(self, name): """动态创建API方法""" if '_' in name: resource, method = name.split('_', 1) if resource in self._available_endpoints: if method in self._available_endpoints[resource]: return lambda **kwargs: self._call_api(resource, method, **kwargs) raise AttributeError(f"未知的API方法: {name}") def __dir__(self): """生成动态的API方法列表""" # 获取基本属性和方法 base_dir = super().__dir__() # 生成动态方法名 dynamic_methods = [] for resource, methods in self._available_endpoints.items(): for method in methods: dynamic_methods.append(f"{resource}_{method}") # 返回完整的列表 return sorted(set(base_dir + dynamic_methods)) def _call_api(self, resource, method, **params): """模拟API调用""" return f"调用API: {method.upper()}{self.base_url}/{resource} 参数={params}"# 使用api = APIClient("https://api.example.com")# 查看可用方法print("可用的API方法:")for method in dir(api): if '_' in method and not method.startswith('_'): print(f" {method}")# 调用动态方法print(api.users_get(id=1))print(api.posts_post(title="新文章"))print(api.comments_put(id=5, content="新评论"))
七、最佳实践和注意事项
1. 方法选择指南
| | |
|---|
| __getattr__ | |
| __getattribute__ | |
| __setattr__ | |
| __delattr__ | |
| __dir__ | |
2. 性能考虑
import timeclass PerformanceTest: def __init__(self): self._data = {} self.normal_attr = "正常属性" def __getattr__(self, name): if name in self._data: return self._data[name] raise AttributeError def __getattribute__(self, name): # 会减慢所有属性访问 return super().__getattribute__(name)# 性能对比obj = PerformanceTest()# 正常属性访问start = time.perf_counter()for _ in range(1000000): x = obj.normal_attrnormal_time = time.perf_counter() - start# 动态属性访问obj._data['dynamic'] = "动态属性"start = time.perf_counter()for _ in range(1000000): x = obj.dynamicdynamic_time = time.perf_counter() - startprint(f"正常属性: {normal_time:.4f}秒")print(f"动态属性: {dynamic_time:.4f}秒")print(f"动态属性比正常属性慢 {dynamic_time/normal_time:.1f} 倍")
3. 安全注意事项
class SafeImplementation: def __init__(self): # 使用双下划线避免无限递归 self.__dict__['_data'] = {} def __getattr__(self, name): # 安全:只处理不存在的属性 if name in self.__dict__['_data']: return self.__dict__['_data'][name] raise AttributeError def __setattr__(self, name, value): # 安全:区分内部属性 if name.startswith('_'): self.__dict__[name] = value else: self.__dict__['_data'][name] = value def __getattribute__(self, name): # 安全:避免递归,使用 super() if name == '_data': return super().__getattribute__(name) return super().__getattribute__(name)
4. 总结要点
性能意识:__getattribute__ 会影响所有属性访问
避免递归:始终使用 super() 调用父类方法
返回NotImplemented:在比较方法中返回 NotImplemented
文档完善:为动态属性提供清晰的文档
一致性:确保所有属性访问行为一致
异常处理:适当抛出 AttributeError
安全性:考虑访问控制和权限验证