在Python的对象模型中,一个关键设计是:实例的属性并非作为固定字段存储在对象内部,而是统一保存在一个名为 __dict__ 的映射结构里。深入理解 __dict__,就是理解Python中实例属性从何而来、如何被查找与销毁,以及实例命名空间的生命周期与作用边界。
一、定义与本质:实例的专属命名空间
__dict__ 是一个字典对象,它存储着实例对象独有的“实例级属性”。
class A:
pass
a = A()
a.x = 10
print(a.__dict__) # 输出:{'x': 10}
type(a.__dict__) # 输出:<class 'dict'>
它本质上就是实例的命名空间,其键为属性名(字符串),值为对应的属性值。每个实例都拥有自己独立的 __dict__,互不干扰:
a1 = A()
a2 = A()
a1.x = 1
a2.x = 2
print(a1.__dict__) # 输出:{'x': 1}
print(a2.__dict__) # 输出:{'x': 2}
二、创建机制:运行时的动态绑定
Python的实例属性无需提前声明。对实例属性的任何赋值操作,都会直接写入其 __dict__。
a.y = 20
上面的操作等价于:
a.__dict__['y'] = 20
这揭示了Python运行时动态绑定的核心特性。__dict__ 是实例属性创建的主要存储位置(不考虑使用了 __slots__ 的特殊情况)。
删除属性同理:
del a.y
# 等价于
del a.__dict__['y']
三、在属性查找中的优先级位置
当你访问 instance.attr 时,Python遵循一个固定的查找链(简化版):
- 实例
instance.__dict__
- 类
Class.__dict__
- 父类
__dict__(按MRO顺序)
- 若仍未找到,触发
__getattr__()
可见,实例 __dict__ 拥有最高优先级。例如:
class A:
x = 100 # 类属性
a = A()
a.x = 10 # 创建实例属性
print(a.x) # 输出:10,访问的是实例属性
print(A.x) # 输出:100,访问的是类属性
原因是 a.__dict__ == {'x': 10},实例属性遮蔽了同名的类属性。
四、生命周期:与实例共存亡
__dict__ 的生命周期与其所属实例对象完全一致。
- 创建时:实例化后即拥有一个空的
__dict__。
a = A()
a.__dict__ == {} # True
- 使用中:随着属性的增删改,
__dict__ 的内容持续变化。
- 销毁时:当实例被垃圾回收,其
__dict__ 也随之销毁,不存在任何“属性残留”。
五、与方法、描述符的关系
-
实例方法不存于__dict__
方法定义在类中,存储于类的 __dict__ 里,通过描述符机制绑定到实例。因此检查 'method_name' in instance.__dict__ 会返回 False。
-
数据描述符的优先级
对于实现了 __get__ 和 __set__ 的数据描述符(如 property),其优先级高于实例 __dict__。
class A:
@property
def x(self):
return 42
a = A()
a.__dict__['x'] = 100 # 尝试直接写入实例字典
print(a.x) # 输出:42,描述符优先
六、与__slots__的互斥关系
当类使用 __slots__ 时,情况发生根本改变:
class A:
__slots__ = ('x',)
a = A()
a.x = 1
hasattr(a, '__dict__') # 输出:False (默认情况下)
__slots__ 会移除实例的 __dict__,将实例属性改为静态存储,从而改变整个命名空间模型。若需显式保留 __dict__,可将其加入 __slots__ 列表。
七、常见误解澄清
- 误解1:实例属性存放在类中。
正解:实例属性只存储在实例自身的 __dict__ 中。
- 误解2:实例
__dict__ 与类 __dict__ 是同一个。
正解:它们是完全独立的命名空间。
- 误解3:方法是实例的一部分。
正解:方法属于类,通过描述符机制进行绑定。
- 误解4:删除实例会影响类属性。
正解:不会。实例 __dict__ 的生命周期与类无关。
总结
__dict__ 是Python实例的核心命名空间,它以字典形式动态存储运行期绑定的属性。其生命周期与实例同步,在属性查找链中通常享有最高优先级。对实例属性的操作,实质是对 __dict__ 的字典操作。掌握 __dict__,是深入理解Python动态对象模型、属性解析机制和内存管理的关键。
