文章目录

Python __getattr__与__getattribute__的调用优先级差异

发布于 2026-05-18 14:46:13 · 浏览 19 次 · 评论 0 条

Python getattrgetattribute的调用优先级差异

理解 __getattr____getattribute__ 的调用顺序是掌握 Python 属性访问机制的关键。一个无条件触发,另一个是后备方案,搞混会导致无限递归或意外行为。


1. 核心差异对比

首先,通过一个表格快速把握两者本质。

特性 __getattribute__ __getattr__
触发时机 无条件触发。每次通过实例访问属性(如 obj.name)时,首先调用此方法。 条件触发。仅在通过常规方式(如查字典)找不到属性时,作为最后手段被调用。
作用 拦截并控制所有属性访问请求。 处理未定义属性的访问,常用于实现动态属性或提供默认值。
风险 高。若在其中访问 self.xxx,极易引发无限递归 低。作为安全的后备机制。
典型用途 实现代理、属性访问日志、严格访问控制。 实现属性默认值、动态属性生成(如从数据库加载)。

2. 调用优先级顺序详解

当执行 obj.x 时,Python 的内部查找顺序如下:

  1. 调用 type(obj).__getattribute__(obj, 'x')
  2. __getattribute__ 内部,通常会按顺序检查:
    • 实例的 __dict__(对象的属性字典)。
    • 类的 __dict__ 及其所有基类的 __dict__(通过描述符协议)。
    • 如果以上步骤都找不到属性 x则最终调用 type(obj).__getattr__(obj, 'x')

简单记忆:__getattribute__ 是必经的第一道大门,__getattr__ 是仓库没找到东西后的失物招领处。


3. 动手验证:一个关键示例

定义一个类,同时重写这两个方法,并加入 print 语句来追踪调用顺序。

class AttributeDemo:
    def __init__(self):
        self.existing_attr = "我存在于实例字典中"

    def __getattribute__(self, name):
        # 必须调用父类(object)的__getattribute__来执行实际查找
        # 否则所有属性访问都会失败,并引发AttributeError
        print(f"__getattribute__ 被调用,查找属性: '{name}'")
        # 注意:这里访问 self.existing_attr 会再次触发__getattribute__(递归风险)
        # 安全做法是直接使用super()
        try:
            return super().__getattribute__(name)
        except AttributeError:
            # 如果常规查找失败(实例和类字典都没有),我们选择让__getattr__处理
            # 此时,__getattribute__方法本身不应引发这个异常
            # 所以我们raise它,由Python的协议转去调用__getattr__
            raise

    def __getattr__(self, name):
        # 只有当__getattribute__中的常规查找失败后才会执行
        print(f"__getattr__ 被调用,属性 '{name}' 未找到")
        return f"为 '{name}' 生成的默认值"

创建实例并访问不同属性

demo = AttributeDemo()

# 情况1:访问一个存在的属性
print("--- 访问存在的属性 ---")
print(f"结果: {demo.existing_attr}")

# 情况2:访问一个不存在的属性
print("\n--- 访问不存在的属性 ---")
print(f"结果: {demo.nonexistent_attr}")

输出结果清晰展示了调用优先级

--- 访问存在的属性 ---
__getattribute__ 被调用,查找属性: 'existing_attr'
结果: 我存在于实例字典中

--- 访问不存在的属性 ---
__getattribute__ 被调用,查找属性: 'nonexistent_attr'
__getattr__ 被调用,属性 'nonexistent_attr' 未找到
结果: 为 'nonexistent_attr' 生成的默认值

4. 常见陷阱与最佳实践

陷阱:在 __getattribute__ 中访问 self.xxx 导致无限递归

__getattribute__ 内部,任何对 self.其他属性 的访问都会再次触发 __getattribute__,形成无限循环。解决方案是使用 super().__getattribute__(xxx) 或直接操作实例的 __dict__

# 错误示范(会递归)
class Bad:
    def __getattribute__(self, name):
        if name == 'y':
            return self.y  # 灾难!再次触发__getattribute__
        return super().__getattribute__(name)

# 正确做法
class Good:
    def __getattribute__(self, name):
        if name == 'y':
            # 方法1:通过父类方法安全获取(如果y存在于字典中)
            # return super().__getattribute__('y')
            # 方法2:直接从实例字典获取
            return self.__dict__.get('y', None)
        return super().__getattribute__(name)

最佳实践

  • 谨慎重写 __getattribute__,通常只在需要绝对控制所有属性访问时(如代理模式、调试)才使用。
  • __getattribute__ 中,务必调用 super().__getattribute__(name) 来执行标准查找,除非你想改变基本的属性访问语义。
  • __getattr__ 作为安全、便捷的后备机制,用于处理缺失属性,代码更清晰、风险更低。
  • 当你需要拦截对特定属性的访问(如计算属性、验证)时,优先考虑使用 @property 描述符,它比无差别拦截的 __getattribute__ 更精准、更安全。

5. 结论:明确的优先级链

属性访问的完整优先级链为:__getattribute__ -> 实例字典 -> 类字典(含描述符) -> __getattr__

__getattribute__ 是守门员,控制着通往所有属性的大门。__getattr__ 是应急方案,只在前门守卫说“这里没有”时才会被启用。明确这个顺序,你就能避免递归深渊,合理利用这两个强大的钩子来设计灵活的 Python 类。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文