用一个可运行示例掌握 Python 面向对象:类、实例与继承

2026-09-26 25 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

面向对象编程(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__ 建立有效状态,用实例方法封装行为,再在确实存在共同接口时引入继承。类并非越多越好;好的对象模型应该减少重复判断,并让业务规则集中在最合适的位置。


相关推荐