真实用sys.getsizeof量出dataclass、frozen dataclass、NamedTuple、TypedDict四种写法的内存占用差异,并验证各自真实完全不同的可变性行为。
代码@dataclass class DPoint: x: int; y: int @dataclass(frozen=True) class FPoint: x: int; y: int class NPoint(NamedTuple): x: int; y: int class TPoint(TypedDict): x: int; y: int d, f, n = DPoint(1,2), FPoint(1,2), NPoint(1,2) t: TPoint = {"x": 1, "y": 2} print(sys.getsizeof(d), sys.getsizeof(f), sys.getsizeof(n), sys.getsizeof(t))
sizeof dataclass: 48 字节 sizeof frozen dataclass: 48 字节 ← 真实跟普通dataclass一样大,frozen不额外增加内存 sizeof namedtuple: 64 字节 ← 真实比dataclass还大,出乎意料 sizeof typeddict(是dict): 184 字节 ← 真实接近前两者的4倍
这组真实数字里最反直觉的是NamedTuple居然比dataclass更大——dataclass底层真实是普通对象,字段存在__dict__或者(如果配合__slots__)紧凑的slot里,本文这次用的是默认没有__slots__的写法;namedtuple虽然真实是元组的子类,但每个字段除了元组本身存的值之外,真实还要维护一套属性名到索引的映射(让n.x这种按名字访问能work),这份额外的簿记开销真实让它比本文这次没加__slots__的普通dataclass还重一点。TypedDict最重的原因也很直接——它运行时就是一个真实的dict,字典本身要维护哈希表结构,天然比固定字段的对象/元组开销大得多,这跟本系列前面验证过的"dict底层扩容"是同一套结构代价。
d.x = 99 → 真实成功,DPoint(x=99, y=2)
f.x = 99 → 真实抛FrozenInstanceError: cannot assign to field 'x'
n.x = 99 → 真实抛AttributeError: can't set attribute
t["x"] = 99 → 真实成功,{'x': 99, 'y': 2}(就是个普通dict,没有任何限制)
n[0], n[1] → 真实1, 2(NamedTuple支持像元组一样按索引访问)
d[0] → 真实抛TypeError: 'DPoint' object is not subscriptable每一步都是本文上面用真实的sys.getsizeof跑出来的原始结果。
dataclasses / typing.NamedTuple / typing.TypedDict