🧩 第四章 面向对象编程技术:去除冗余、进行整理
📄 一句话概括

同一个类造 10000 个实例:made 只有一个、no 有 10000 个——这就是类变量和实例变量的分水岭。

📝 本节和前面几节的关系

4.5 讲盒子怎么开口,这一节讲一个盒子能复制出多少个。

一、为什么这条重要

只有一个对象,那叫单例;类之所以是类,是它能造很多个。 而且每个都要有自己的状态——否则就不叫 10000 个,叫 1 个被反复覆盖。 这一条同时支撑了多态和内存两件事:每个对象自己算,每个对象自己占一块。

二、实测 10000 个

ts = [Ticket(i) for i in range(10000)]。 实测:造出 10000 个,编号 0 到 9999,互不相同。 Ticket.made 是 10000,ts[0].no 是 0,ts[9999].no 是 9999。 同一个类,一万份状态,各不相干。

三、两个名字,两种东西

made 写在类里——类变量,只有 1 个,全体共享。 no 写在 self 上——实例变量,每个实例 1 个。 同一个名字在类和实例上,是两个不同的东西。4.7 专门讲这件事。 判断办法:在 __init__ 里 self.x = ... 的是实例变量;写在类体里的是类变量。

四、这条功能支撑了什么

多态要靠它——每个对象按自己的类型算,前提是它有自己的状态。 内存那一章要靠它——每次 new 都占一块堆,第 5 章讲这块怎么分。 没有【创建很多个】,前两条都无从谈起。

五、动手 Demo:类的功能之三——创建很多个

一个类造 10000 个实例,看每个是不是都有自己的状态。 再对比类变量和实例变量:同一个类里,一个只有一个,一个有 10000 个。

class Ticket:
    made = 0          # 类变量:全体共享
    def __init__(self, no):
        self.no = no  # 实例变量:每个一份
        Ticket.made += 1

ts = [Ticket(i) for i in range(10000)]
print(Ticket.made, ts[0].no, ts[-1].no)
print(len(set(t.no for t in ts)))

实跑输出:

--------------------------------------------------------------------
类的功能之三——创建很多个
--------------------------------------------------------------------

  同一个类,可以造出很多个各自独立的实例。**这是类区别于【单例对象】的关键。**
  实测造 10000 个,看每个是不是都有自己的状态:

      ts = [Ticket(i) for i in range(10000)]

      造出来的个数      10000
      第一个编号        0
      最后一个编号      9999
      编号互不相同吗    是

  **注意 `made` 和 `no` 的区别**:

           写在哪                  有几个
  -----------------------------------------------------
  made     类里(类变量)          只有 1 个,全体共享
  no       self 上(实例变量)     每个实例 1 个

  实测确认:**`made` 是 10000,`ts[0].no` 是 0,`ts[9999].no` 是 9999。**
  **同一个名字在类上和实例上,是两个不同的东西**——4.7 专门讲这件事。

实测:made 是 10000,ts[0].no 是 0,ts[9999].no 是 9999,编号互不相同。 同一个名字在类上和实例上,是两个不同的东西——made 只有一份分享,no 每个一份。

30 秒实验

  1. 给你的类加一个计数器,看它统计的是实例个数还是某个字段之和。
  2. 想一想:如果 made 写成了 self.made,会发生什么。

常见误区

  1. 以为类只能有一个对象。造很多个才是类的常态。
  2. 以为类变量和实例变量是同一件事。一个共享、一个各有一份。
自测三问(点开对答案) 1. 造 10000 个实例后,`made` 和 `no` 各是什么? —— `made` 是 10000(类变量,只有一份共享);`no` 每个实例一份,从 0 到 9999。 2. 怎么区分类变量和实例变量? —— 写在类体里的是类变量;在 `__init__` 里用 `self.x = ...` 赋值的是实例变量。 3. 这条功能支撑了哪两件事? —— 多态(每个对象按自己的类型算)和内存(每次 new 都占一块堆)。

相关笔记