《深入 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 = 1、setattr(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 的世界。

84

被折叠的 条评论
为什么被折叠?



