在 Python 中,__getattr__ 是一个特殊方法(魔术方法),用于在访问对象的属性或方法失败时提供自定义处理逻辑。它在属性查找的最后一步被调用,当对象本身、其类及父类中都找不到指定属性时,Python会调用这个方法。
基本语法
def __getattr__(self, name):
# 处理属性访问的逻辑
return value
基本用法
class DynamicAttributes:
def __init__(self):
self.existing_attr = "我是已存在的属性"
def __getattr__(self, name):
# 当访问不存在的属性时调用
return f"动态创建的属性: {name}"
# 使用示例
obj = DynamicAttributes()
print(obj.existing_attr) # 输出: 我是已存在的属性
print(obj.non_existing) # 输出: 动态创建的属性: non_existing
print(obj.any_name) # 输出: 动态创建的属性: any_name
__getattr__ vs __getattribute__
| 特性 | __getattr__ |
__getattribute__ |
|---|---|---|
| 调用时机 | 只在属性不存在时调用 | 每次属性访问都调用 |
| 性能 | 较高 | 较低 |
| 使用场景 | 动态属性、代理模式 | 属性访问控制、日志记录 |
class CompareExample:
def __init__(self):
self.normal_attr = "正常属性"
def __getattr__(self, name):
print(f"__getattr__ 被调用: {name}")
return f"动态属性: {name}"
def __getattribute__(self, name):
print(f"__getattribute__ 被调用: {name}")
return super().__getattribute__(name)
obj = CompareExample()
print("--- 访问已存在属性 ---")
print(obj.normal_attr)
print("--- 访问不存在属性 ---")
print(obj.dynamic_attr)
实际应用场景
1. 动态属性创建
class Config:
def __init__(self, config_dict):
self._config = config_dict
def __getattr__(self, name):
if name in self._config:
return self._config[name]
raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")
# 使用示例
config_data = {
'host': 'localhost',
'port': 8080,
'debug': True
}
config = Config(config_data)
print(config.host) # 输出: localhost
print(config.port) # 输出: 8080
print(config.debug) # 输出: True
2. 代理模式
可以用于创建代理对象,转发属性访问到被代理对象:
class Proxy:
def __init__(self, target):
self._target = target # 被代理的对象
def __getattr__(self, name):
# 将属性访问转发给被代理对象
return getattr(self._target, name)
class RealObject:
def __init__(self):
self.value = 42
def greet(self):
return "Hello from RealObject"
real = RealObject()
proxy = Proxy(real)
print(proxy.value) # 输出: 42 (转发到 real.value)
print(proxy.greet()) # 输出: Hello from RealObject (转发到 real.greet())
3. API 代理
import requests
class APIProxy:
def __init__(self, base_url):
self.base_url = base_url
def __getattr__(self, endpoint):
def method(*args, **kwargs):
url = f"{self.base_url}/{endpoint}"
response = requests.get(url, params=kwargs)
return response.json()
return method
# 使用示例
# api = APIProxy("https://api.example.com")
# users = api.users(limit=10) # 相当于访问 https://api.example.com/users?limit=10
4. 惰性加载
class LazyLoader:
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]
# 使用示例
loader = LazyLoader()
print(loader.data1) # 输出: 正在加载 data1... 已加载的数据: data1
print(loader.data1) # 输出: 已加载的数据: data1 (从缓存中获取)
print(loader.data2) # 输出: 正在加载 data2... 已加载的数据: data2
5. 向后兼容性
class ModernClass:
def __init__(self):
self.new_attribute = "新属性"
def __getattr__(self, name):
# 为已重命名的旧属性提供向后兼容性
old_new_mapping = {
'old_name': 'new_attribute',
'deprecated_method': 'new_method'
}
if name in old_new_mapping:
import warnings
warnings.warn(f"属性 '{name}' 已弃用,请使用 '{old_new_mapping[name]}'",
DeprecationWarning)
return getattr(self, old_new_mapping[name])
raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")
# 使用示例
obj = ModernClass()
print(obj.old_name) # 会显示警告,但能正常工作
高级用法
1. 链式调用模拟
class Chainable:
def __getattr__(self, name):
self.last_operation = name
return self
def execute(self):
print(f"执行操作: {self.last_operation}")
return self
# 使用示例
chain = Chainable()
chain.select.from_table.where("id=1").execute()
2. 属性验证和转换
class ValidatedAttributes:
def __init__(self):
self._values = {}
def __getattr__(self, name):
if name.startswith('get_'):
attr_name = name[4:]
if attr_name in self._values:
return self._values[attr_name]
raise AttributeError(f"属性 '{name}' 不存在")
# 使用示例
obj = ValidatedAttributes()
obj._values = {'name': 'Alice', 'age': 30}
print(obj.get_name) # 输出: Alice
print(obj.get_age) # 输出: 30
注意事项
1. 避免无限递归
class BadExample:
def __getattr__(self, name):
# 错误:会导致无限递归
return self.name # 这会再次调用 __getattr__
class GoodExample:
def __getattr__(self, name):
# 正确:使用 super() 或直接返回值
return f"属性 {name} 的值"
# 或者使用 __getattribute__ 配合 super()
def __getattribute__(self, name):
try:
return super().__getattribute__(name)
except AttributeError:
return f"回退值: {name}"
2. 性能考虑
import time
class PerformanceExample:
def __init__(self):
self._cache = {}
def __getattr__(self, name):
# 昂贵的操作
time.sleep(0.1) # 模拟耗时操作
value = f"计算出的 {name}"
self._cache[name] = value
return value
# 对于频繁访问的属性,考虑预先计算或使用缓存
最佳实践
- 明确用途:只在确实需要动态属性时使用
__getattr__ - 提供清晰的错误信息:在适当的时候抛出
AttributeError - 考虑性能影响:对频繁访问的属性使用缓存
- 保持一致性:如果实现了
__getattr__,考虑是否也需要__setattr__ - 文档化行为:让使用者了解类的动态特性
class WellDesignedClass:
"""
一个使用 __getattr__ 的示例类
这个类支持动态属性访问,任何不存在的属性
都会返回一个格式化的字符串。
"""
def __init__(self, prefix="动态属性"):
self.prefix = prefix
def __getattr__(self, name):
"""处理不存在的属性访问"""
if name.startswith('_'):
raise AttributeError(f"无法访问私有属性 '{name}'")
return f"{self.prefix}: {name}"
__getattr__ 是 Python 动态特性的强大工具,合理使用可以创建灵活且强大的 API,但需要谨慎使用以避免混淆和性能问题。