Python 类与对象(选修):看见实例怎样保存独立状态
选修章节:用 Product 商品库存图解 class、实例、__init__、self、属性和方法,并判断什么时候字典加函数已经足够。
本文目录 6 节 · 点击展开
本章是选修。 完成前面的变量、容器、判断、循环、函数、文件和异常后,你已经能写完整的数据处理脚本;不学习类,也不影响完成 Python 入门主线。
类适合解决一种更具体的问题:脚本里反复出现同一类对象,每个对象需要长期保存自己的状态,并且有多项行为总在操作这些状态。若只是读取 JSON、筛选订单、计算一次金额,字典加函数通常更直接。
先决定:字典加函数已经够用吗
一个商品只有 SKU、库存和补货判断时,下面的写法完全正确:
product = {
"sku": "K161",
"stock": 3,
}
def need_restock(product):
return product["stock"] < 5
def add_stock(product, quantity):
product["stock"] += quantity
print(need_restock(product))
add_stock(product, 5)
print(product["stock"])
数据保存在字典里,函数从外部接收字典并操作它。对于几十行的临时脚本,这种关系一眼就能看懂。
product = {“sku”: …, “stock”: …}need_restock(product)add_stock(product, 5)- 更适合
- 数据来自 JSON,结构临时,操作数量少
- 优点
- 代码直接,和外部数据形状接近
- 信号
- 函数显式接收要处理的字典
class Productproduct = Product(“K161”, 3)product.add_stock(5)- 更适合
- 同类对象反复出现,并长期保留各自状态
- 优点
- 和状态紧密相关的多项行为有固定入口
- 信号
- 通过当前实例调用自己的方法
判断标准:状态和行为需要长期一起存在,类才开始自然;只是为了让代码“看起来正式”,不要增加类。
用一个完整 Product 类贯穿本章
下面是本章唯一的主类。代码可以直接复制运行:
class Product:
def __init__(self, sku, stock):
self.sku = sku
self.stock = stock
def need_restock(self):
return self.stock < 5
def add_stock(self, quantity):
self.stock = self.stock + quantity
product1 = Product("K161", 3)
product2 = Product("K162", 10)
print(product1.sku, product1.stock, product1.need_restock())
print(product2.sku, product2.stock, product2.need_restock())
product1.add_stock(5)
print(product1.sku, product1.stock, product1.need_restock())
print(product2.sku, product2.stock, product2.need_restock())
输出:
K161 3 True
K162 10 False
K161 8 False
K162 10 False
这段程序只需要先认识五个词:
| 词 | 本例 | 作用 |
|---|---|---|
| 类 | Product | 定义这一类对象保存什么数据、能做什么 |
| 实例 / 对象 | product1、product2 | 根据类创建出来的两个具体商品 |
| 属性 | self.sku、self.stock | 当前实例自己保存的数据 |
| 方法 | need_restock()、add_stock() | 当前实例可以执行的行为 |
self | 正在初始化或调用方法的那个实例 | 让方法找到“这一个对象”的属性 |
Product("K161", 3) 怎样变成一个实例
调用类时,Python 先准备一个新的 Product 实例,再调用 __init__ 写入初始状态。__init__ 的职责是初始化,不是把实例 return 出来。
Product("K161", 3) 的初始化轨迹- 01 · 调用位置
product1 = Product(“K161”, 3)请求创建一个新的 Product 实例。
- 02 · 新实例
self → 新的 Product 对象Python 准备空实例,并把它作为 self 交给初始化方法。
- 03 · 参数进入
sku → “K161”stock → 3调用中的两个实参进入 init 的参数名。
- 04 · 写入属性
self.sku = skuself.stock = stock右边读取参数值,左边把值保存到当前实例。
- 05 · 实例完成
product1 → Product 实例sku: “K161” · stock: 3初始化结束,调用表达式得到实例并绑定给 product1。
最容易混淆的是:
self.stock = stock
- 右边的
stock是这次调用传进__init__的参数,当前值为3。 - 左边的
self.stock是新实例自己的属性,以后可以通过product1.stock读取。
调用 Product("K161", 3) 时,不需要手工传 self。Python 自动让 self 指向当前正在初始化的新实例。
两个实例为什么能保存独立状态
再次调用 Product("K162", 10) 时,Python 会准备另一个新实例。两个对象都按照 Product 的定义拥有 sku 和 stock,但属性值各自保存在自己的实例中。
现在只调用:
product1.add_stock(5)
调用方法时,Python 自动让 self 指向点号左边的 product1,并让参数 quantity 得到 5。
product1K161stock = 3product2K162stock = 10两个名字指向两个不同实例,各自拥有 stock 属性。
product1.add_stock(5)- self
→ product1- quantity
→ 5
self.stock = self.stock + quantity代入当前对象:3 + 5 → 8。
product1K161stock: 3 → 8product2K162stock: 10 → 10 · 未改变方法只通过 self 修改 product1,没有访问 product2。
方法可以读取当前实例的属性:
def need_restock(self):
return self.stock < 5
product1.need_restock() 中的 self.stock 读取 product1 当前的库存;product2.need_restock() 则读取 product2 的库存。调用者不传 self,但需要通过具体实例调用方法。
什么时候应该停留在字典和函数
继续使用字典和函数,通常更适合:
- 数据直接来自 JSON,只需读取、筛选、转换后写出。
- 脚本很短,数据只是一次性中间结果。
- 只有一两个简单操作,函数参数已经清楚。
- 类只会包住一个乘法或一条判断,没有长期状态。
可以考虑类的信号是:
- 同一种实体会创建很多个实例。
- 每个实例需要在程序运行期间持续保存、改变自己的状态。
- 多个方法都围绕同一组属性工作。
product.add_stock(5)这种调用比反复传字典更符合业务阅读方式。
不要为了一个乘法创建 AmountCalculator,也不要把每个字典都机械转换成对象。类增加了定义、初始化和方法调用三层结构,只有它们换来了更清楚的状态边界才值得。
动手练习:沿着 self 追踪状态
复制完整 Product 程序,先预测再运行:
- 创建
product3 = Product("K163", 0),写出调用__init__时self、sku、stock分别指向什么。 - 调用
product3.add_stock(2),写出属性变化前后的值。 - 确认 product1 和 product2 的库存没有被 product3 的方法调用改变。
- 给 Product 增加
remove_stock(quantity),只修改当前实例的库存。 - 回到最开始的字典版本:如果脚本只执行一次补货判断,你会选哪种写法?说明理由。