《深入 Python 对象协议:getattr、getattribute、setattr 调用顺序与避免无限递归的实战指南》

2025博客之星年度评选已开启 10w+人浏览 2.9k人参与

《深入 Python 对象协议:getattr、getattribute、setattr 调用顺序与避免无限递归的实战指南》

在我教授 Python 的这些年里,关于对象属性访问机制的问题,几乎每一届学生都会问到:
“老师,getattr 和 getattribute 到底谁先调用?”
“为什么我重写了 __getattribute__ 就无限递归了?”
“setattr 是什么时候触发的?能不能拦截所有属性赋值?”

这些问题看似细节,却深刻影响着我们对 Python 对象模型的理解。掌握它们,你会发现 Python 的动态性远比你想象得更强大,也更优雅。

今天这篇文章,我希望带你从基础到进阶,完整理解 Python 属性访问背后的机制,并通过大量代码示例与实战案例,帮助你真正掌握如何安全、优雅地使用这些魔法方法。


一、开篇:为什么要理解 getattr / getattribute / setattr?

Python 之所以被称为“胶水语言”,很大程度上源于它的 动态对象模型
你可以在运行时:

  • 动态添加属性
  • 拦截属性访问
  • 修改对象行为
  • 实现代理模式、ORM、序列化框架、依赖注入等高级功能

这些能力的核心基础,就是 Python 的 属性访问协议(Attribute Access Protocol)

理解 __getattr____getattribute____setattr__ 的调用顺序,是掌握 Python 元编程的第一步。


二、基础知识:三个魔法方法的职责

1. __getattribute__(self, name)

  • 所有属性访问都会首先调用它
  • 无条件触发
  • 即便属性不存在也会调用

它是 Python 属性访问链的入口。

2. __getattr__(self, name)

  • 仅当属性不存在时才调用
  • 是兜底方案(fallback)

常用于:

  • 提供默认值
  • 动态生成属性
  • 实现代理模式

3. __setattr__(self, name, value)

  • 所有属性赋值都会调用
  • 包括 self.x = 1setattr(obj, 'x', 1)

常用于:

  • 属性校验
  • 数据绑定
  • ORM 字段管理

三、核心问题:三者的调用顺序是什么?

这是本文的核心之一。

属性访问顺序(读取)

当你执行:

obj.x

Python 的调用顺序是:

1. __getattribute__(self, 'x')
    ↓
2. 如果 __getattribute__ 抛出 AttributeError:
    调用 __getattr__(self, 'x')

也就是说:

  • __getattribute__ 永远先调用
  • __getattr__ 只有在属性不存在时才调用

属性赋值顺序(写入)

当你执行:

obj.x = 10

Python 会:

1. 调用 __setattr__(self, 'x', 10)

没有 fallback,也不会调用 __getattribute__


四、为什么会出现无限递归?

看下面这个例子:

class A:
    def __getattribute__(self, name):
        print("getattribute:", name)
        return self.__dict__[name]

a = A()
a.x = 1
print(a.x)

运行后你会看到:

RecursionError: maximum recursion depth exceeded

为什么?

因为:

self.__dict__

本身也是一个属性访问,会再次触发 __getattribute__,于是无限递归。


五、如何避免无限递归?(关键技巧)

1. 使用 object.__getattribute__ 访问原始实现

正确写法:

class A:
    def __getattribute__(self, name):
        print("getattribute:", name)
        return object.__getattribute__(self, name)

这是最常见、最安全的方式。

2. 在 __setattr__ 中避免递归

错误写法:

def __setattr__(self, name, value):
    self.name = value  # 递归!

正确写法:

def __setattr__(self, name, value):
    object.__setattr__(self, name, value)

3. 在 __getattr__ 中返回默认值时也要避免递归

错误写法:

def __getattr__(self, name):
    return self.default  # default 不存在又触发 __getattr__

正确写法:

def __getattr__(self, name):
    return object.__getattribute__(self, 'default')

六、完整示例:安全拦截属性访问

下面是一个安全、可扩展的属性拦截器:

class AttrLogger:
    def __getattribute__(self, name):
        print(f"[GET] {name}")
        return object.__getattribute__(self, name)

    def __getattr__(self, name):
        print(f"[GETATTR fallback] {name}")
        return f"<Missing attribute: {name}>"

    def __setattr__(self, name, value):
        print(f"[SET] {name} = {value}")
        object.__setattr__(self, name, value)

测试:

a = AttrLogger()
a.x = 10
print(a.x)
print(a.y)

输出:

[SET] x = 10
[GET] x
10
[GET] y
[GETATTR fallback] y
<Missing attribute: y>

七、进阶:利用属性拦截实现动态代理(Proxy)

属性拦截最常见的应用之一,就是实现代理模式。

例如,我们想创建一个对象,它能把所有属性访问转发给另一个对象:

class Proxy:
    def __init__(self, target):
        object.__setattr__(self, '_target', target)

    def __getattribute__(self, name):
        target = object.__getattribute__(self, '_target')
        print(f"Proxy GET {name}")
        return getattr(target, name)

    def __setattr__(self, name, value):
        target = object.__getattribute__(self, '_target')
        print(f"Proxy SET {name} = {value}")
        setattr(target, name, value)

测试:

class User:
    def __init__(self):
        self.name = "Alice"

u = User()
p = Proxy(u)

print(p.name)
p.name = "Bob"
print(u.name)

输出:

Proxy GET name
Alice
Proxy SET name = Bob
Bob

这就是 ORM、RPC 框架常用的设计方式。


八、实战案例:用 getattr / setattr 实现轻量级 ORM 字段管理

下面是一个简化版 ORM 模型:

class Field:
    def __init__(self, default=None):
        self.default = default

class Model:
    def __init__(self):
        object.__setattr__(self, '_data', {})

    def __getattribute__(self, name):
        fields = object.__getattribute__(self, '_data')
        if name in fields:
            return fields[name]
        return object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        fields = object.__getattribute__(self, '_data')
        fields[name] = value

使用:

class User(Model):
    name = Field(default="Unknown")
    age = Field(default=0)

u = User()
u.name = "Alice"
print(u.name)

你会发现:

  • 所有字段都被存储在 _data
  • 属性访问被拦截并重定向

这是 Django ORM、SQLAlchemy 的核心思想之一。


九、最佳实践:如何优雅地使用属性拦截?

1. 永远使用 object.__getattribute__ / object.__setattr__

避免递归的唯一正确方式。

2. 不要滥用属性拦截

它会:

  • 降低可读性
  • 增加调试难度
  • 影响性能

3. 用于明确的场景

例如:

  • 动态代理
  • ORM 字段管理
  • 数据绑定
  • 延迟加载(lazy loading)
  • 访问控制(只读属性)

4. 避免在 __getattribute__ 中做复杂逻辑

因为它会拦截所有属性访问,包括内部方法。


十、前沿视角:属性拦截在现代 Python 中的应用

1. FastAPI / Pydantic

大量使用属性拦截实现:

  • 字段校验
  • 动态生成模型
  • 自动序列化

2. SQLAlchemy

利用属性拦截实现:

  • 延迟加载
  • 字段代理
  • 动态查询构建

3. AI 框架(如 PyTorch)

通过属性拦截实现:

  • 模型参数管理
  • 自动梯度跟踪

属性拦截是现代 Python 框架的核心能力之一。


十一、总结

本文我们从基础到进阶,完整讲解了:

  • __getattribute____getattr____setattr__ 的职责与调用顺序
  • 为什么会出现无限递归
  • 如何正确避免递归
  • 如何利用属性拦截实现代理、ORM 等高级功能
  • 最佳实践与前沿应用

如果你能真正理解这些内容,你已经迈入 Python 元编程的大门。


十二、互动讨论

我很想听听你的经验:

  • 你在使用 __getattribute__ 时遇到过哪些坑?
  • 你是否尝试过用属性拦截实现某种框架或工具?
  • 你觉得 Python 的对象模型未来还会有哪些演进?

欢迎在评论区分享你的故事,我们一起交流、一起成长。


如果你愿意,我还可以继续为你写:

  • 更深入的元类(metaclass)教程
  • Python 对象模型全景图
  • 代理模式、ORM 框架的完整实现

告诉我你的方向,我会陪你继续探索 Python 的世界。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值