ZetCode

Python __getattr__ 方法

最后修改于 2025 年 4 月 8 日

这份综合指南探讨了 Python 的 __getattr__ 方法,这是处理属性访问的特殊方法。我们将涵盖基本用法、动态属性、回退机制、代理模式和实际示例。

基本定义

当属性查找失败时,会调用 __getattr__ 方法。它充当属性访问的回退机制。该方法将属性名称作为参数,并且应返回一个值或引发 AttributeError。

关键特征:它仅针对缺失的属性调用,可以动态计算值,并且是 Python 属性访问协议的一部分。它与拦截所有属性访问的 __getattribute__ 不同。

基本的 __getattr__ 实现

这是一个简单的实现,展示了 __getattr__ 如何作为缺失属性的回退工作。 它演示了基本语法和行为。

basic_getattr.py
class DynamicAttributes:
    def __getattr__(self, name):
        if name == 'color':
            return 'blue'
        raise AttributeError(f"'DynamicAttributes' has no attribute '{name}'")

obj = DynamicAttributes()
print(obj.color)  # 'blue'
# print(obj.size)  # Raises AttributeError

此示例显示 __getattr__ 处理对未定义属性的请求。 当访问 'color' 时,它返回 'blue'。 其他名称会引发 AttributeError,这是推荐的做法。

该方法接收属性名称作为字符串。 您可以实现任何逻辑来计算或获取该值。 始终为无效名称引发 AttributeError。

动态属性计算

__getattr__ 可以根据模式或命名约定动态计算属性。 这对于具有可预测属性名称的 API 很有用。

dynamic_computation.py
class DynamicCalculator:
    def __getattr__(self, name):
        if name.startswith('calc_'):
            operation = name[5:]
            def calculator(*args):
                return f"Calculated {operation} of {args}"
            return calculator
        raise AttributeError(f"No such attribute: {name}")

calc = DynamicCalculator()
print(calc.calc_sum(1, 2, 3))  # "Calculated sum of (1, 2, 3)"
print(calc.calc_product(4, 5)) # "Calculated product of (4, 5)"

此类基于以 'calc_' 开头的属性名称动态创建计算方法。 返回的函数在其输出中使用操作名称。

该示例演示了 __getattr__ 如何按需创建并返回函数。 这种模式用于许多 ORM 和 API 包装器中。

延迟属性加载

__getattr__ 可以实现昂贵资源的延迟加载,仅在首次访问时才获取它们,并为将来使用进行缓存。

lazy_loading.py
class LazyLoader:
    def __init__(self):
        self._cache = {}
    
    def __getattr__(self, name):
        if name not in self._cache:
            print(f"Loading {name}...")
            self._cache[name] = f"Value of {name}"
        return self._cache[name]

loader = LazyLoader()
print(loader.data1)  # "Loading data1..." then "Value of data1"
print(loader.data1)  # "Value of data1" (from cache)

此实现仅在首次访问时才加载属性,并将它们存储在缓存字典中。 后续访问将返回缓存的值。

这种模式对于诸如数据库连接或文件加载之类的昂贵操作很有用。 它通过延迟工作直到需要时来优化性能。

代理模式实现

__getattr__ 可以将属性访问转发到另一个对象,从而实现代理模式。 这对于包装器和装饰器很有用。

proxy_pattern.py
class Proxy:
    def __init__(self, target):
        self._target = target
    
    def __getattr__(self, name):
        return getattr(self._target, name)

class Target:
    def __init__(self, value):
        self.value = value
    def method(self):
        return f"Target method: {self.value}"

target = Target(42)
proxy = Proxy(target)
print(proxy.value)   # 42
print(proxy.method()) # "Target method: 42"

Proxy 类使用 getattr 将所有属性访问转发到其目标对象。 这会在目标周围创建一个透明的包装器。

代理可以在转发调用之前/之后添加行为。 这种模式用于模拟对象、远程处理和访问控制系统中。

类似字典的属性访问

__getattr__ 可以提供对数据的类似字典的访问,同时保持对象语法。 这会创建一个混合的对象-字典接口。

dict_access.py
class DictLikeObject:
    def __init__(self, data):
        self._data = data
    
    def __getattr__(self, name):
        if name in self._data:
            return self._data[name]
        raise AttributeError(f"No attribute or key '{name}'")

data = {'name': 'Alice', 'age': 30, 'city': 'London'}
obj = DictLikeObject(data)
print(obj.name)  # 'Alice'
print(obj.age)   # 30
# print(obj.job) # Raises AttributeError

此类包装一个字典并将其键公开为属性。 它结合了字典存储和对象访问语法。

该实现会在引发 AttributeError 之前检查字典。 这种模式用于配置系统和 JSON 对象包装器中。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我在编程教学方面拥有超过十年的经验。

列出所有 Python 教程