面向对象编程(Object-Oriented Programming,OOP)把数据和操作数据的行为组织在一起。在 Python 中,类负责描述对象的结构与能力,实例则代表程序运行时真正参与工作的具体对象。理解类、实例化和继承之后,就能更自然地建模订单、用户、设备、支付方式等业务概念。
类是定义,实例才是具体对象
类可以看作一份对象定义。下面的 Product 类声明了商品需要保存的数据,并提供计算总价的方法:
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
def total_price(self, quantity: int) -> float:
return self.price * quantity
keyboard = Product("Mechanical Keyboard", 499.0)
mouse = Product("Wireless Mouse", 199.0)
print(keyboard.name, keyboard.total_price(2))
print(mouse.name, mouse.total_price(3))
运行方式:
python app.py
这里有三个关键概念:
Product是类,描述同一类对象共有的数据和行为。keyboard与mouse是通过Product(...)创建的两个实例。self指向当前实例,因此不同实例拥有各自的name和price。
__init__ 常被称为初始化方法。当执行 Product("Mechanical Keyboard", 499.0) 时,Python 创建实例,并调用 __init__ 设置它的初始状态。
继承复用接口,也允许改变行为
当多个类型具有共同能力,但实现方式不同,可以考虑继承。例如,不同配送方式都需要计算运费,但普通配送和加急配送可以采用不同规则。
下面是一份可以直接保存为 shipping.py 并运行的完整示例:
from typing import List
class ShippingMethod:
def __init__(self, name: str, base_fee: float):
if base_fee < 0:
raise ValueError("base_fee cannot be negative")
self.name = name
self.base_fee = base_fee
def calculate(self, weight_kg: float) -> float:
if weight_kg <= 0:
raise ValueError("weight_kg must be greater than zero")
return self.base_fee + weight_kg * 2.0
def describe(self) -> str:
return f"{self.name}: base fee {self.base_fee:.2f}"
class ExpressShipping(ShippingMethod):
def __init__(self, base_fee: float, speed_multiplier: float = 1.5):
super().__init__("Express shipping", base_fee)
self.speed_multiplier = speed_multiplier
def calculate(self, weight_kg: float) -> float:
standard_fee = super().calculate(weight_kg)
return standard_fee * self.speed_multiplier
class FreeShipping(ShippingMethod):
def __init__(self, minimum_order: float):
super().__init__("Free shipping", 0.0)
self.minimum_order = minimum_order
def calculate_for_order(self, weight_kg: float, order_total: float) -> float:
if order_total >= self.minimum_order:
return 0.0
return super().calculate(weight_kg)
methods: List[ShippingMethod] = [
ShippingMethod("Standard shipping", 8.0),
ExpressShipping(12.0),
]
for method in methods:
print(method.describe())
print(f"Fee for 3 kg: {method.calculate(3):.2f}")
free_shipping = FreeShipping(minimum_order=200.0)
print(f"Small order: {free_shipping.calculate_for_order(2, 120):.2f}")
print(f"Large order: {free_shipping.calculate_for_order(2, 260):.2f}")
执行:
python shipping.py
这个示例展示了继承中的几个核心动作:
ExpressShipping(ShippingMethod)表示加急配送继承基础配送类。super().__init__(...)调用父类的初始化逻辑,避免重复设置公共属性。- 子类重新定义
calculate(),称为方法重写。 super().calculate(weight_kg)先复用父类算法,再增加加急倍率。- 不同对象都能响应
calculate(),调用方不必为每一种配送类型编写独立分支。
这种统一调用方式是多态的常见表现:代码依赖共同接口,而不是依赖某个具体子类。
把状态约束放在对象边界上
类不仅用于收纳字段,还可以维护有效状态。示例在初始化时拒绝负数基础运费,在计算时拒绝非正重量。这样,错误会在靠近数据入口的位置暴露,而不是在后续账单计算中悄悄扩散。
可以继续增加只读属性,让内部数据不被随意修改:
class Account:
def __init__(self, owner: str, balance: float = 0.0):
if balance < 0:
raise ValueError("initial balance cannot be negative")
self.owner = owner
self._balance = balance
@property
def balance(self) -> float:
return self._balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("deposit must be positive")
self._balance += amount
account = Account("Alice", 100.0)
account.deposit(50.0)
print(account.balance)
下划线开头的 _balance 表示它是内部实现细节。Python 不会强制禁止外部访问,但这种命名约定提醒调用方:余额应通过 deposit() 等方法改变,以便保留校验规则。
什么时候不必使用继承
继承适合表达稳定的“是一种”关系,例如“加急配送是一种配送方式”。如果只是临时复用几个函数,或者两个类型碰巧拥有相似字段,继承反而可能制造紧耦合。
可以用下面的检查清单做决定:
- 子类是否确实可以替代父类使用?
- 父类的公开方法对所有子类是否都有合理含义?
- 共享行为是否稳定,而不是频繁随业务条件变化?
- 使用组合,即让一个对象持有另一个对象,是否会更清晰?
学习 Python OOP 时,可以先从小型类开始:用 __init__ 建立有效状态,用实例方法封装行为,再在确实存在共同接口时引入继承。类并非越多越好;好的对象模型应该减少重复判断,并让业务规则集中在最合适的位置。