📄 一句话概括
造 10000 个实例,类对象只有 1 个——方法代码只存一遍,每个实例只多自己那几个字段。
📝 本节和前面几节的关系
5.6 说数据搬到了堆上,这一节说类本身放在哪、有几份。
一、类信息放哪
类信息放在静态区(类信息区),程序一启动就加载,只加载一次。 它里面有三样:字段的定义、方法的代码、指向父类的指针。 实例里只放一个指向它的指针——这就是 5.1 说的【顺着类去找方法】。
二、实测
造 10000 个实例,set(id(type(t)) for t in ts) 只有 1 个。
类上定义的字段+方法是 2 项(kind、area),只有一份。
而一万个实例各自的数据是 10000 份。
一万比一——这就是 OOP 省内存的全部秘密。
三、省在哪
方法代码只存一遍:一万个实例共享同一段 area 的代码。
每个实例只多几个字段:w、h 各一格。
反过来说:如果每个实例都复制一份方法代码,一万个实例早就爆了。
四、这条的性质
它是【必须】,不是【优化】。没有它,类的概念就不成立。 它也解释了为什么改一个方法,所有实例立刻都变——因为只有一份代码。 同一件事从另一面看就是风险:父类改错,全家族一起错(2.4 讲过的代价)。
五、动手 Demo:每个类只加载一个类信息
造一万个实例,数一数有几个不同的类对象。 再对比类上定义了几项、实例各自带了什么。
ts = [Rect(i, i) for i in range(10000)]
print(len(ts)) # 10000 个实例
print(len(set(id(type(t)) for t in ts))) # 几个类对象
print(len([k for k in Rect.__dict__
if not k.startswith("__")])) # 类上几项
实跑输出:
--------------------------------------------------------------------
每个类只加载一个类信息
--------------------------------------------------------------------
造一万个实例,**类信息会加载几份?**实测:
造出来的实例数 10000
不同的类对象有几个 1
类上定义的字段+方法 2 项
一万个实例各自的数据 10000 份
东西 有几份 在哪
--------------------------------------------------------------------
类信息(字段定义、方法代码) 1 份 静态区/类信息区
实例数据(self 上的字段) 每个实例 1 份 堆
**一万个实例共享同一份类信息**——所以方法代码只存了一遍。
**每个实例只多出【自己那几个字段】**,这是 OOP 省内存的关键。
**反过来说:如果每个实例都复制一份方法代码,一万个实例就爆了。**
实测:一万个实例,类对象只有 1 个;类上 2 项,实例各自只多自己那几个字段。 方法代码只存一遍——如果每个实例都复制一份方法,一万个实例就爆了。
30 秒实验
- 给一个类加一个方法,看已经造好的实例是不是立刻能调(说明只有一份代码)。
- 改一个方法,看所有实例的行为是不是一起变。
常见误区
- 以为每个实例都有一份方法。方法在类上,只有一份。
- 以为类信息也放在堆上。它在静态区/类信息区,程序启动时加载。
自测三问(点开对答案)
1. 类信息放在哪、有几份? —— 放在静态区(类信息区),程序启动时加载一次,只有一份。 2. 造一万个实例后实测是什么样? —— 类对象只有 1 个,类上 2 项定义;实例各自 1 份数据,共 10000 份。 3. 这条性质的另一面是什么? —— 改一个方法所有实例立刻都变——好处是省内存、改一处全生效,风险是父类改错全家一起错。- ☐ 下一节(5.8 每次创建实例都会使用堆区)
相关笔记
- 每次创建实例都会使用堆区 — 实例每次新开一块
- 继承对共同点和不同点进行系统的分类和整理 — 改父类全家族变
- ☕ Java基础 · 索引 — 类加载机制
- 🧩 面向对象是怎样工作的 · 索引