Python的“欺骗性”语法:为什么说 obj.name 本质上就是 obj.getter()?
引言:一个“错误”的直觉
从初学者的视角切入:我们通常认为 self.name = name 就是把数据存进字典,self.name 就是把数据取出来。但在处理复杂对象(如Django模型、Pydantic、@property)时,这种理解是完全错误的。在Python的高级世界里,. 和 = 只是表象,真正的幕后黑手是 Getter 和 Setter。
第一层洋葱:@property 的伪装
展示一段标准的 @property 代码:
1 | class Student: |
当我们访问 s.name 时,看起来像是在访问变量,但实际上是在执行函数。@property 让我们误以为在操作属性,其实是在调用方法。
第二层洋葱:揭开 property 的面纱(描述符协议)
@property 只是一个实现了 get 和 set 方法的类(描述符)。让我们手写一个简单的 MyProperty 类,实现 get 和 set:
1 | class MyProperty: |
当我们在类中定义 name = MyProperty() 时,外部的 obj.name 是如何自动触发内部的 get 的?
终极奥义:点号的底层翻译
用伪代码或底层视角展示 Python 解释器是如何“翻译”代码的:
- 读取时:
obj.name-> 查找类字典 -> 发现是描述符 -> 调用__get__(即 Getter)。 - 赋值时:
obj.name = 1-> 查找类字典 -> 发现是描述符 -> 调用__set__(即 Setter)。
强调:这就是为什么你不能在 setter 里直接写 self.name = ...,因为那会再次触发 __set__,导致无限递归(死循环)。你必须操作真正的存储介质(如 _name 或 __dict__)。
为什么要这么设计?(升华主题)
这种机制带来的巨大优势:接口与实现的解耦。你可以把“存储逻辑”(存数据库、写文件、计算)隐藏在“访问语法”(点号)之下,调用者完全无感知。这也是 ORM(如SQLAlchemy)和 数据验证库(如Pydantic)能够存在的基石。
总结
从简单的赋值到复杂的描述符,Python的属性访问机制远比表面看起来复杂。下次写 self.x = y 时,想一想,这背后是否藏着一个 __set__ 在等着你?
Python的这种设计,让代码既简洁直观,又能灵活应对复杂场景,体现了其“优雅”的设计哲学。

