类属性属于类本身、全体实例共享,实例属性属于单个实例;
__dict__是二者的命名空间载体,__slots__则用固定槽位替代字典,换取内存与结构约束。
类属性与实例属性
- 类属性:直接定义在类体中(不在任何方法内),属于类对象本身,所有实例共享同一份
- 实例属性:定义在
__init__等方法中,通过self.xxx绑定,属于单个实例对象,每个实例独立一份
class A:
class_attr = "类属性" # 类属性:类定义加载时创建
def __init__(self, val):
self.instance_attr = val # 实例属性:实例化时创建| 维度 | 类属性 | 实例属性 |
|---|---|---|
| 归属 | 类对象 | 单个实例 |
| 创建时机 | 类定义加载时 | 实例化(__init__ 执行)时 |
| 内存占用 | 全类仅一份 | 每个实例各存一份 |
| 修改影响 | 通过类修改同步所有实例;实例赋值则生成同名实例属性覆盖 | 仅影响当前实例 |
经典坑:可变类属性
「共享可变对象」的机制根源见 引用与可变性。
类属性为列表、字典等可变对象时,实例原地修改会影响所有实例:
class B:
lst = [] # 可变类属性
b1 = B()
b2 = B()
b1.lst.append(1) # 原地修改共享的列表
print(b2.lst) # [1],所有实例受影响若执行 b1.lst = [2],则是给 b1 新建同名实例属性,从此遮蔽类属性、不再共享。
属性查找顺序
访问 obj.attr 遵循先实例后类:
- 实例自身的命名空间
- 类的命名空间
- 沿继承链(MRO)向上查找父类
- 全部失败则触发
__getattr__或抛出AttributeError
完整七步流程见属性访问与查找。
__dict__ 命名空间
__dict__ 是对象的命名空间字典,存储其全部属性(变量、方法),但不同对象的类型与特性完全不同。
类的 __dict__:只读代理
print(type(A.__dict__)) # <class 'mappingproxy'>- 类型为
types.MappingProxyType,是只读映射代理,不允许直接增删改键,保护类的命名空间 - 存放类属性、所有方法、特殊方法等
- 底层真实字典仍可通过类的
__setattr__等机制修改,代理仅提供只读视图
实例的 __dict__:可写字典
a = A("测试")
print(type(a.__dict__)) # <class 'dict'>- 原生
dict,可读写,动态存储实例的全部自定义属性 - 仅用户自定义类的实例默认拥有
__dict__
没有 __dict__ 的对象
object()实例:object是最顶层基类,实例极简,为节省内存不附带__dict__,无法动态新增属性:
o = object()
o.abc = 1 # AttributeError: 'object' object has no attribute 'abc'- 定义了
__slots__的类实例:默认禁用实例__dict__,改用固定槽位存储属性。
__slots__ 机制
原理与作用
默认情况下,Python 为每个实例创建 __dict__ 哈希表存储属性,开销显著。__slots__ 声明实例允许拥有的固定属性名列表,CPython 改用紧凑的结构体数组存储属性,替代字典。
核心作用:
- 大幅降低内存占用:大量实例场景下节省可达数倍,是数据类、ORM 实体的核心优化手段
- 禁止动态新增属性:实例只能使用声明的属性,约束对象结构,避免随意赋值
- 属性访问速度小幅提升:数组索引访问快于字典哈希查找
基础用法
class Person:
__slots__ = ["name", "age"] # 声明实例仅允许 name、age 两个属性
p = Person()
p.name = "张三"
p.age = 20
p.gender = "男" # AttributeError,无法新增未声明属性
print(p.__dict__) # AttributeError,默认无实例字典四大坑点
- 不自动继承:父类定义
__slots__后,子类默认仍会生成__dict__、可动态新增属性;子类也需限制时必须重新定义,最终有效属性是父子 slots 的并集 - 保留
__dict__:在列表中显式加入'__dict__',可在使用 slots 的同时保留动态属性能力,但内存节省效果大打折扣
class C:
__slots__ = ["name", "__dict__"]- 弱引用:slots 实例默认不支持弱引用,需要时须将
'__weakref__'加入列表 - 不影响类属性:
__slots__仅约束实例属性,类本身仍可自由新增类属性