第7章 面向对象编程

679 阅读30分钟

面向对象编程(Object-Oriented Programming, OOP)是现代软件开发的核心范式之一,它通过封装、继承和多态三大特性,实现了代码的模块化、可复用性和可扩展性。Python 作为一门支持多范式的编程语言,对面向对象编程提供了全面支持,且其简洁灵活的语法使得 OOP 思想能够自然落地。

变量与数据类型构成程序设计的基础,而类与对象则构建了面向对象编程的核心体系。类通过封装数据与行为定义抽象模板,对象通过实例化实现具体实体,二者共同支撑起复杂系统的构建。本章将系统阐述面向对象编程的核心概念、类的设计原则及工程实践方法,结合典型场景提供示例,帮助读者建立严谨而深入的认知。

7.1 面向对象编程基础

面向对象编程的核心是将现实世界中的实体抽象为 "对象",每个对象包含描述其特征的 "属性" 和描述其行为的 "方法"。这种思想相比面向过程编程(以函数为核心),更符合人类对复杂系统的认知方式,是构建大型软件的高效范式。

7.1.1 三大核心特性

面向对象编程的三大核心特性 —— 封装、继承和多态,构成了其区别于其他编程范式的本质特征,也是实现代码复用与扩展的基础。

  1. 封装(Encapsulation) 将数据(属性)和操作数据的方法绑定在一起,隐藏内部实现细节,仅通过公开接口与外部交互。这一机制既保证了数据的安全性,又降低了模块间的耦合度。

    Python 通过命名规范和属性装饰器实现封装:

    • _属性:约定为受保护属性,提示外部不应直接访问

    • __属性:私有属性,Python 会进行名称修饰以限制直接访问

    • @property:将方法转换为属性,实现可控的访问接口

    class BankAccount:
        """银行账户类:封装余额和操作方法"""
        def __init__(self, account_id: str):
            self._account_id = account_id  # 受保护属性(约定不直接访问)
            self.__balance = 0.0  # 私有属性(Python会改名以限制直接访问)
    
        @property
        def balance(self) -> float:
            """公开接口:查询余额"""
            return self.__balance
    
        def deposit(self, amount: float) -> None:
            """公开接口:存款(包含数据校验)"""
            if amount <= 0:
                raise ValueError("存款金额必须为正数")
            self.__balance += amount
    
        def withdraw(self, amount: float) -> None:
            """公开接口:取款(包含数据校验)"""
            if amount <= 0:
                raise ValueError("取款金额必须为正数")
            if amount > self.__balance:
                raise ValueError("余额不足")
            self.__balance -= amount
    

    使用示例:

    # 只能通过公开接口操作,无法直接修改私有属性
    account = BankAccount("622202******1234")
    account.deposit(1000)
    print(account.balance)  # 输出:1000.0
    account.withdraw(500)
    print(account.balance)  # 输出:500.0
    # account.__balance = 100000  # 报错:无法直接访问私有属性
    

    📌 重点提示:封装的核心价值在于 "信息隐藏"—— 外部无需了解内部实现细节,只需通过公开接口交互,这显著降低了代码维护成本,当内部实现变更时,只要接口不变,外部代码就无需修改。

  2. 继承(Inheritance) 允许一个类(子类)继承另一个类(父类)的属性和方法,并可新增或重写方法,实现代码复用。继承建立了类之间的层次关系,使系统结构更加清晰。

    Python 通过class 子类(父类):语法实现单继承,也支持多继承(需谨慎使用)。

    class Animal:
        """动物基类:定义通用属性和方法"""
        def __init__(self, name: str):
            self.name = name
    
        def eat(self) -> None:
            print(f"{self.name}正在进食")
    
        def sleep(self) -> None:
            print(f"{self.name}正在睡觉")
    
    class Dog(Animal):
        """狗类:继承Animal,新增特有方法"""
        def bark(self) -> None:
            print(f"{self.name}在汪汪叫")
    
    class Cat(Animal):
        """猫类:继承Animal,重写方法并新增特有方法"""
        def sleep(self) -> None:  # 重写父类方法
            print(f"{self.name}在阳光下打盹")
    
        def meow(self) -> None:  # 新增特有方法
            print(f"{self.name}在喵喵叫")
    

    使用示例:

    # 子类拥有父类的方法,同时有自己的特性
    dog = Dog("旺财")
    dog.eat()   # 继承父类方法:输出"旺财正在进食"
    dog.bark()  # 子类特有方法:输出"旺财在汪汪叫"
    
    cat = Cat("咪咪")
    cat.sleep()  # 重写后的方法:输出"咪咪在阳光下打盹"
    cat.meow()   # 子类特有方法:输出"咪咪在喵喵叫"
    

    📌 重点提示:继承应遵循 "is-a" 关系(如 Dog is a Animal),避免为了代码复用而滥用继承。当类之间是 "has-a" 关系时(如 Car has a Engine),应使用组合而非继承。

  3. 多态(Polymorphism) 不同类的对象对同一方法调用呈现不同行为,即 "同一接口,多种实现"。多态提高了代码的灵活性和扩展性,支持动态绑定(运行时确定调用哪个方法)。

    Python 通过方法重写和鸭子类型(duck typing)实现多态:

    • 方法重写:子类实现与父类同名的方法
    • 鸭子类型:不依赖继承关系,只关注是否实现特定方法
    # 多态示例:同一接口处理不同对象
    def make_sound(animal: Animal) -> None:
        """接收Animal类型(或其子类),调用对应声音方法"""
        if isinstance(animal, Dog):
            animal.bark()
        elif isinstance(animal, Cat):
            animal.meow()
    
    make_sound(dog)  # 输出:"旺财在汪汪叫"
    make_sound(cat)  # 输出:"咪咪在喵喵叫"
    
    # 鸭子类型示例:不依赖继承,只要实现了quack()即可
    class Duck:
        def quack(self) -> None:
            print("鸭子嘎嘎叫")
    
    class RubberDuck:
        def quack(self) -> None:
            print("橡皮鸭吱吱叫")
    
    def make_quack(duck_like):
        """接收任何实现了quack()的对象"""
        duck_like.quack()
    
    make_quack(Duck())      # 输出:"鸭子嘎嘎叫"
    make_quack(RubberDuck())  # 输出:"橡皮鸭吱吱叫"
    

    📌 重点提示:鸭子类型体现了 Python 的灵活性 ——"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。这种动态类型特性使得代码更加灵活,但也要求开发者更注重接口设计的一致性。

7.1.2 类与对象的关系

类与对象是面向对象编程的基本概念,理解二者的关系是掌握 OOP 的基础:

  • 类(Class) :是对象的抽象模板,定义了对象的属性和方法(如 "汽车类" 定义了所有汽车共有的特征:轮子、颜色、行驶方法等)。类是静态的,不占用内存空间。
  • 对象(Object) :是类的具体实例(如 "我的红色特斯拉" 是 "汽车类" 的一个对象)。对象是动态的,创建时会在内存中分配空间。
  • 关系:类 → 实例化 → 对象,一个类可以创建多个对象,每个对象拥有独立的属性值但共享类的方法。
# 类(模板)
class Car:
    def __init__(self, color: str, brand: str):
        self.color = color  # 属性
        self.brand = brand  # 属性
        
    def drive(self) -> None:  # 方法
        print(f"{self.color}{self.brand}正在行驶")

# 对象(实例)
car1 = Car("红色", "特斯拉")  # 实例化
car2 = Car("蓝色", "宝马")    # 实例化

# 每个对象有独立的属性值
print(car1.color)  # 输出:红色
print(car2.color)  # 输出:蓝色

# 共享类的方法
car1.drive()  # 输出:红色特斯拉正在行驶
car2.drive()  # 输出:蓝色宝马正在行驶

📌 重点提示:类是创建对象的蓝图,对象是类的具体实现。这种抽象与具体的关系,使得我们可以通过类来批量创建具有相同结构但不同状态的对象,极大提高了代码的复用性。

7.2 类的定义与使用

在 Python 中,类的定义简洁灵活,支持属性、方法、装饰器等特性,能够满足从简单到复杂的设计需求。掌握类的完整结构与各类成员的使用方法,是进行面向对象编程的基础。

7.2.1 类的基本结构

一个完整的类通常包含类属性、实例属性、实例方法、类方法、静态方法和特殊方法,这些成员共同构成了类的功能体系。

class Car:
    """汽车类:展示类的基本结构"""
    # 类属性:所有实例共享的值
    wheels = 4  # 所有汽车都有4个轮子

    def __init__(self, brand: str, color: str):
        """构造方法:初始化实例属性"""
        self.brand = brand  # 实例属性:品牌
        self.color = color  # 实例属性:颜色
        self._mileage = 0  # 受保护的实例属性:里程数

    # 实例方法:操作实例属性,第一个参数必须是self(指向实例本身)
    def drive(self, distance: float) -> None:
        """驾驶一定距离,更新里程数"""
        if distance > 0:
            self._mileage += distance
            print(f"{self.color}{self.brand}行驶了{distance}公里")

    # 类方法:操作类属性,用@classmethod装饰,第一个参数是cls(指向类本身)
    @classmethod
    def change_wheels(cls, count: int) -> None:
        """修改所有汽车的轮子数量(仅作示例,实际不合理)"""
        cls.wheels = count

    # 静态方法:与类和实例无关,用@staticmethod装饰,无默认参数
    @staticmethod
    def honk() -> None:
        """鸣笛(不依赖类或实例属性)"""
        print("嘀嘀——")

    # 属性装饰器:将方法转为只读属性
    @property
    def mileage(self) -> float:
        """获取里程数(只读)"""
        return self._mileage

使用示例:

if __name__ == "__main__":
    # 创建实例(对象)
    my_car = Car("特斯拉", "红色")
    your_car = Car("宝马", "蓝色")

    # 访问类属性(所有实例共享)
    print(f"汽车轮子数:{Car.wheels}")  # 输出:4
    print(f"我的车轮子数:{my_car.wheels}")  # 输出:4

    # 调用实例方法
    my_car.drive(50)  # 输出:"红色特斯拉行驶了50公里"
    your_car.drive(30)  # 输出:"蓝色宝马行驶了30公里"

    # 访问实例属性(通过@property)
    print(f"我的车里程:{my_car.mileage}公里")  # 输出:50.0

    # 调用类方法(修改类属性)
    Car.change_wheels(6)
    print(f"修改后轮子数:{your_car.wheels}")  # 输出:6

    # 调用静态方法
    Car.honk()  # 输出:"嘀嘀——"
    my_car.honk()  # 输出:"嘀嘀——"

7.2.2 方法类型详解

类中的方法分为实例方法、类方法和静态方法三种类型,每种方法有其特定的用途和调用方式。

  1. 实例方法实例方法是最常用的方法类型,用于操作实例属性,其第一个参数必须是self(代表实例本身),通过self可以访问实例的属性和其他实例方法。

    应用场景:需要访问或修改对象状态的操作,如用户信息的更新、订单状态的变更等。

    class User:
        """用户类:演示实例方法的使用"""
        def __init__(self, name: str, age: int):
            self.name = name
            self.age = age
            
        def get_user_info(self) -> str:
            """获取用户信息(实例方法)"""
            return f"姓名:{self.name},年龄:{self.age}"
            
        def birthday(self) -> None:
            """年龄增加1岁(修改实例状态)"""
            self.age += 1
            print(f"{self.name}生日快乐!现在{self.age}岁了")
    
    # 使用示例
    user = User("张三", 20)
    print(user.get_user_info())  # 输出:"姓名:张三,年龄:20"
    user.birthday()  # 输出:"张三生日快乐!现在21岁了"
    
  2. 类方法类方法用@classmethod装饰器标识,第一个参数是cls(代表类本身),用于操作类属性或创建类的实例。类方法可通过类名或实例调用。

    典型场景:

    • 提供多种实例化方式(如从不同数据源创建对象)

    • 操作类级别的属性

    • 作为工厂方法创建类的实例

    class Date:
        """日期类:演示类方法的实例化场景"""
        def __init__(self, year: int, month: int, day: int):
            self.year = year
            self.month = month
            self.day = day
    
        @classmethod
        def from_string(cls, date_str: str):
            """从字符串(如"2024-05-20")创建Date实例"""
            year, month, day = map(int, date_str.split("-"))
            return cls(year, month, day)  # 调用构造方法创建实例
            
        @classmethod
        def today(cls):
            """创建表示今天的Date实例"""
            from datetime import datetime
            now = datetime.now()
            return cls(now.year, now.month, now.day)
    
    # 使用类方法创建实例
    date1 = Date(2024, 5, 20)
    date2 = Date.from_string("2024-05-20")  # 更灵活的实例化方式
    today = Date.today()  # 创建今天的日期对象
    print(date1.year == date2.year)  # 输出:True
    
  3. 静态方法静态方法用@staticmethod装饰器标识,没有默认参数(既不接收self也不接收cls),逻辑上属于类,但不依赖类或实例的状态。

    应用场景:提供与类相关的工具函数,这些函数不需要访问类属性或实例属性。

    class MathUtils:
        """数学工具类:演示静态方法的使用"""
        
        @staticmethod
        def is_prime(n: int) -> bool:
            """判断一个数是否为质数(静态方法)"""
            if n <= 1:
                return False
            for i in range(2, int(n**0.5) + 1):
                if n % i == 0:
                    return False
            return True
            
        @staticmethod
        def factorial(n: int) -> int:
            """计算阶乘(静态方法)"""
            if n < 0:
                raise ValueError("阶乘仅支持非负整数")
            result = 1
            for i in range(1, n+1):
                result *= i
            return result
    
    # 使用示例
    print(MathUtils.is_prime(17))  # 输出:True
    print(MathUtils.factorial(5))  # 输出:120
    

    📌 重点提示:静态方法与类方法的区别在于是否接收cls参数。静态方法更接近普通函数,只是逻辑上属于某个类;而类方法则与类本身紧密相关,能够操作类属性或创建类的实例。

7.2.3 属性装饰器:@property

@property装饰器用于将方法转换为 "只读属性",既保留了方法的逻辑(如数据校验、动态计算),又能像属性一样访问,是实现封装的重要工具。

基本用法:

class User:
    """用户类:演示@property的基本用法"""
    def __init__(self, birth_year: int):
        self._birth_year = birth_year  # 存储出生年份

    @property
    def age(self) -> int:
        """计算年龄(动态属性,无需手动更新)"""
        from datetime import datetime
        return datetime.now().year - self._birth_year

# 使用
user = User(1990)
print(user.age)  # 输出:34(假设当前是2024年)

高级用法:通过@属性名.setter定义修改方法,实现 "可读可写" 属性;通过@属性名.deleter定义删除方法。

class User:
    """用户类:演示@property的高级用法"""
    def __init__(self, birth_year: int):
        self._birth_year = birth_year  # 存储出生年份

    @property
    def birth_year(self) -> int:
        """出生年份(只读)"""
        return self._birth_year

    @birth_year.setter
    def birth_year(self, value: int):
        """出生年份的 setter(用于控制修改逻辑)"""
        from datetime import datetime
        current_year = datetime.now().year
        if value < 1900 or value > current_year:
            raise ValueError(f"出生年份必须在1900-{current_year}之间")
        self._birth_year = value
        
    @birth_year.deleter
    def birth_year(self):
        """删除出生年份属性"""
        del self._birth_year

# 使用
user = User(1995)
print(user.birth_year)  # 输出:1995

user.birth_year = 2000  # 通过setter修改,会进行校验
print(user.birth_year)  # 输出:2000

del user.birth_year  # 删除属性

📌 重点提示:@property的核心价值在于提供了统一的属性访问接口,同时隐藏了内部实现细节。当需要修改属性的计算方式时,只需修改对应的方法,而无需改变外部代码的访问方式,这显著提高了代码的可维护性。

7.3 继承与多态进阶

继承允许类之间共享代码,而多态则允许不同类的对象以统一的方式交互。合理使用这两个特性,能显著提升代码的复用性和扩展性,是构建复杂系统的关键。

7.3.1 继承的层次与方法重写

  • 继承层次:形成类的 "父子" 关系树(如AnimalMammalDog),下层类继承上层类的所有非私有成员。
  • 方法重写:子类可重新实现父类的方法,覆盖父类行为,是多态的基础。
class Shape:
    """形状基类:定义通用方法"""
    def area(self) -> float:
        """计算面积(基类未实现,由子类重写)"""
        raise NotImplementedError("子类必须实现area()方法")

    def perimeter(self) -> float:
        """计算周长(基类未实现,由子类重写)"""
        raise NotImplementedError("子类必须实现perimeter()方法")

class Rectangle(Shape):
    """矩形类:继承Shape并实现面积和周长计算"""
    def __init__(self, length: float, width: float):
        self.length = length
        self.width = width

    def area(self) -> float:  # 重写父类方法
        return self.length * self.width

    def perimeter(self) -> float:  # 重写父类方法
        return 2 * (self.length + self.width)

class Circle(Shape):
    """圆形类:继承Shape并实现面积和周长计算"""
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:  # 重写父类方法
        import math
        return math.pi * self.radius **2

    def perimeter(self) -> float:  # 重写父类方法
        import math
        return 2 * math.pi * self.radius

多态应用示例:

# 多态:统一接口处理不同形状
def print_shape_info(shape: Shape) -> None:
    print(f"面积:{shape.area():.2f},周长:{shape.perimeter():.2f}")

rect = Rectangle(3, 4)
circle = Circle(2)

print_shape_info(rect)   # 输出:面积:12.00,周长:14.00
print_shape_info(circle)  # 输出:面积:12.57,周长:12.57

📌 重点提示:通过抽象基类(ABC)可以更严格地定义接口。使用abc模块的ABC@abstractmethod装饰器,可强制子类实现特定方法,避免因遗漏实现而导致的运行时错误。

7.3.2 多继承与 MRO(方法解析顺序)

Python 支持多继承(一个类继承多个父类),但可能导致 "菱形问题"(多个父类继承自同一基类时的方法调用冲突)。为此,Python 使用MRO(Method Resolution Order,方法解析顺序)  规则(C3 线性化算法)确定方法调用顺序。

class A:
    def show(self) -> None:
        print("A的show方法")

class B(A):
    def show(self) -> None:
        print("B的show方法")

class C(A):
    def show(self) -> None:
        print("C的show方法")

class D(B, C):
    """多继承:继承B和C"""
    pass

# 查看MRO:方法调用顺序
print(D.mro())  # 输出:[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

d = D()
d.show()  # 输出:"B的show方法"(按MRO顺序调用B的show)

在多继承中,可以使用super()函数调用父类的方法,super()会根据 MRO 顺序查找下一个类的方法:

class D(B, C):
    def show(self) -> None:
        print("D的show方法")
        super().show()  # 调用MRO中下一个类的show方法(B的show)
        
d = D()
d.show()
# 输出:
# D的show方法
# B的show方法

📌 重点提示:多继承增加了代码复杂度,除非必要(如混入类),否则应优先使用单继承 + 组合模式。如果必须使用多继承,需清晰了解 MRO 规则,避免出现意外的方法调用顺序。

7.3.3 组合优于继承

当类之间是 "有一个" 而非 "是一个" 的关系时,组合(将一个类的实例作为另一个类的属性)比继承更灵活,更能适应需求变化。

反例:用继承实现 "汽车有引擎" 的关系(不合理,因为引擎不是汽车的一种):

class Engine:
    def start(self) -> None:
        print("引擎启动")

class Car(Engine):  # 错误:汽车"有一个"引擎,而非"是一个"引擎
    pass

正例:用组合实现合理的关联关系:

class Engine:
    def start(self) -> None:
        print("引擎启动")

    def stop(self) -> None:
        print("引擎关闭")

class Car:
    def __init__(self):
        self.engine = Engine()  # 组合:汽车"有一个"引擎

    def start(self) -> None:
        print("汽车启动流程:")
        self.engine.start()  # 调用引擎的方法
        
    def stop(self) -> None:
        print("汽车停止流程:")
        self.engine.stop()  # 调用引擎的方法

car = Car()
car.start()  # 输出:"汽车启动流程:引擎启动"
car.stop()   # 输出:"汽车停止流程:引擎关闭"

组合的优势:

  1. 避免了继承层次过深的问题
  2. 允许动态替换组件(如更换不同类型的引擎)
  3. 降低了类之间的耦合度
# 支持不同类型的引擎
class ElectricEngine(Engine):
    def start(self) -> None:
        print("电动引擎启动(安静无声)")

# 动态更换引擎
car = Car()
car.engine = ElectricEngine()  # 更换为电动引擎
car.start()  # 输出:"汽车启动流程:电动引擎启动(安静无声)"

📌 重点提示:"组合优于继承" 是面向对象设计的重要原则。继承是一种强耦合关系(子类依赖父类的实现),而组合是一种弱耦合关系(通过接口交互),更能适应需求的变化。在设计类之间的关系时,应优先考虑组合。

7.4 类的特殊方法与工程实践

类的特殊方法(又称 "魔术方法")是 Python 面向对象编程的 "隐藏武器",它们以双下划线__开头和结尾(如__init____str__),用于自定义对象与 Python 内置语法的交互方式。这些方法赋予了开发者 "改造"Python 语法的能力 —— 让自定义类支持运算符运算、容器行为、上下文管理等特性,使代码更贴近自然语言,同时提升可读性与可维护性。

7.4.1 常用特殊方法与典型应用场景

特殊方法的设计遵循 "协议导向" 原则 —— 实现特定的特殊方法组合,即可让类满足某种 "协议"(如迭代器协议、上下文管理器协议),从而具备对应的内置行为。以下按工程中使用频率排序,介绍核心特殊方法及其应用。

1.** 对象生命周期管理:__new__ 与 __init____del__**- __new__:类的实例化入口,负责创建对象(返回实例),是唯一在实例创建前执行的方法,常用于单例模式、不可变类型定制等场景。

  • __init__:对象初始化方法,在__new__之后执行,负责设置实例属性(无返回值)。
  • __del__:对象销毁时触发的析构方法,用于释放资源(如关闭文件、断开网络连接),但依赖垃圾回收机制,不建议过度依赖。

工程案例:单例模式实现单例模式要求类只能创建一个实例(如全局配置对象),通过__new__控制实例创建过程:

class GlobalConfig:
    """全局配置类(单例模式):确保程序中只有一个配置实例"""
    _instance = None  # 存储唯一实例

    def __new__(cls, *args, **kwargs):
        """控制实例创建:仅当实例不存在时才创建"""
        if cls._instance is None:
            # 调用父类的__new__创建实例
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, config_path: str):
        """初始化配置(仅首次创建时执行)"""
        if not hasattr(self, "_initialized"):  # 避免重复初始化
            self.config = self._load_config(config_path)
            self._initialized = True

    def _load_config(self, path: str) -> dict:
        """加载配置文件(模拟实现)"""
        print(f"从{path}加载配置")
        return {"timeout": 30, "max_retry": 3}

# 测试:多次实例化仍返回同一对象
config1 = GlobalConfig("config.json")
config2 = GlobalConfig("another_config.json")  # 不会重新加载配置
print(config1 is config2)  # 输出:True(同一实例)

2.** 字符串表示协议:__str__ 与 __repr__**- __str__:返回 "用户友好" 的字符串(通过str(obj)print(obj)调用),用于展示给终端用户或写入日志。

  • __repr__:返回 "开发者友好" 的字符串(通过repr(obj)调用),包含对象的完整构造信息,用于调试和序列化。

工程规范

  • 所有业务实体类(如UserOrder)都应实现__str__,确保日志输出有意义;
  • 复杂数据结构类应实现__repr__,便于调试时快速重建对象。
class Transaction:
    """交易记录类:实现字符串表示协议"""
    def __init__(self, txn_id: str, amount: float, status: str):
        self.txn_id = txn_id
        self.amount = amount
        self.status = status

    def __str__(self) -> str:
        """用户友好格式:用于日志展示"""
        return f"交易[{self.txn_id}]:{self.amount}元({self.status})"

    def __repr__(self) -> str:
        """开发者友好格式:用于调试"""
        return (f"Transaction(txn_id='{self.txn_id}', "
                f"amount={self.amount}, status='{self.status}')")

txn = Transaction("TXN2024001", 199.99, "success")
print(txn)        # 输出:交易[TXN2024001]:199.99元(success)(日志友好)
print(repr(txn))  # 输出:Transaction(txn_id='TXN2024001', amount=199.99, status='success')(调试友好)

3.** 容器协议:__len____getitem____setitem____contains__** 实现这些方法可让类模拟列表、字典等容器的行为,支持len()、索引访问(obj[i])、成员判断(x in obj)等操作,常用于自定义数据集、缓存结构等场景。

工程案例:分页数据容器实现一个支持分页的数据集,像列表一样索引访问,内部自动处理分页逻辑:

class PaginatedDataset:
    """分页数据集:模拟列表行为,仅加载当前页数据"""
    def __init__(self, data: list, page_size: int = 10):
        self._data = data  # 全量数据
        self._page_size = page_size  # 每页数据量

    def __len__(self) -> int:
        """返回总数据量"""
        return len(self._data)

    def __getitem__(self, index: int) -> any:
        """支持索引访问:index为全局索引,自动映射到对应页"""
        if not isinstance(index, int):
            raise TypeError("索引必须是整数")
        if index < 0 or index >= len(self):
            raise IndexError("索引超出范围")
        # 计算索引所在的页和页内位置
        page = index // self._page_size
        page_index = index % self._page_size
        # 模拟“加载分页数据”(实际场景可能从数据库/缓存读取)
        start = page * self._page_size
        end = start + self._page_size
        page_data = self._data[start:end]
        return page_data[page_index]

    def __contains__(self, item: any) -> bool:
        """支持`in`判断:检查元素是否在全量数据中"""
        return item in self._data

# 使用示例:像列表一样操作分页数据
dataset = PaginatedDataset(list(range(100)), page_size=10)
print(len(dataset))       # 输出:100(总数据量)
print(dataset[15])        # 输出:15(访问全局索引15,自动取第2页数据)
print(99 in dataset)      # 输出:True(判断元素是否存在)

4.** 运算符重载:__add____sub____eq__ 等 ** 通过重载运算符,可让自定义类支持+-==等运算,使代码更直观。工程中常用于数学对象(向量、矩阵)、业务量(金额、时间)等场景。

工程案例:金额类(避免浮点精度问题) 实现一个Money类,支持金额相加、比较,内部用分作为整数存储以避免浮点误差:

class Money:
    """金额类:精确处理货币计算,避免浮点误差"""
    def __init__(self, yuan: float):
        self._cents = int(round(yuan * 100))  # 转换为分(整数)

    def __add__(self, other: "Money") -> "Money":
        """重载+:金额相加"""
        if not isinstance(other, Money):
            raise TypeError("仅支持Money类型之间的加法")
        return Money(self._cents / 100 + other._cents / 100)

    def __sub__(self, other: "Money") -> "Money":
        """重载-:金额相减"""
        if not isinstance(other, Money):
            raise TypeError("仅支持Money类型之间的减法")
        return Money(self._cents / 100 - other._cents / 100)

    def __eq__(self, other: "Money") -> bool:
        """重载==:金额相等判断"""
        if not isinstance(other, Money):
            return False
        return self._cents == other._cents

    def __str__(self) -> str:
        """格式化显示为元"""
        return f"¥{self._cents / 100:.2f}"

# 使用示例:精确计算金额
m1 = Money(0.1)
m2 = Money(0.2)
print(m1 + m2)  # 输出:¥0.30(避免0.1+0.2=0.30000000000000004的浮点误差)
print(m1 == Money(0.10))  # 输出:True(精确比较)
  1. 上下文管理器协议:__enter__ 与 __exit__

实现这两个方法可让类支持with语句,用于 "自动资源管理"—— 进入with块时获取资源,退出时自动释放(即使发生异常),是文件操作、数据库连接等场景的最佳实践。

工程案例:安全文件操作实现一个自动关闭的文件操作类,避免手动调用close()导致的资源泄露:

class SafeFile:
    """安全文件操作类:支持with语句,自动关闭文件"""
    def __init__(self, file_path: str, mode: str = "r"):
        self._file_path = file_path
        self._mode = mode
        self._file = None  # 文件句柄

    def __enter__(self) -> "SafeFile":
        """进入with块:打开文件并返回实例"""
        self._file = open(self._file_path, self._mode)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
        """退出with块:关闭文件,处理异常"""
        if self._file:
            self._file.close()
        # 若发生异常,返回True表示已处理(不向上传播),False表示继续传播
        if exc_type is not None:
            print(f"文件操作出错:{exc_val}")
            return True  # 抑制异常
        return False

    # 封装文件读写方法
    def read(self, size: int = -1) -> str:
        return self._file.read(size)

    def write(self, content: str) -> int:
        return self._file.write(content)

# 使用示例:with块内自动管理文件生命周期
with SafeFile("data.txt", "w") as f:
    f.write("Hello, 上下文管理器!")  # 写入内容
# 退出with块后,文件自动关闭(无需手动调用close())

# 测试异常处理
with SafeFile("nonexistent.txt", "r") as f:
    f.read()  # 触发文件不存在异常
# 输出:文件操作出错:[Errno 2] No such file or directory: 'nonexistent.txt'(异常被捕获)

7.4.2 工程实践:类设计的核心原则

在大型项目中,类的设计质量直接决定代码的可维护性与扩展性。遵循 SOLID 设计原则,可避免常见的设计陷阱,创建出健壮、灵活的系统。

  1. 单一职责原则(Single Responsibility Principle)

定义:一个类应仅负责一项核心功能,避免 "全能类"。

工程价值:降低类的复杂度,减少修改时的 "连锁反应"(修改一个功能不影响其他功能)。

反例UserOrderService类同时处理用户管理(创建 / 更新用户)和订单处理(创建 / 支付订单),两个功能耦合导致修改用户逻辑可能影响订单功能。

正例:拆分为两个单一职责的类,通过组合关联:

# 单一职责:仅处理用户管理
class UserService:
    def create_user(self, user_info: dict) -> str:
        """创建用户,返回用户ID"""
        # 实现逻辑...
        return "user_123"
        
    def get_user(self, user_id: str) -> dict:
        """获取用户信息"""
        # 实现逻辑...
        return {"id": user_id, "name": "Alice"}

# 单一职责:仅处理订单,依赖UserService验证用户
class OrderService:
    def __init__(self, user_service: UserService):
        self.user_service = user_service  # 组合:依赖抽象而非具体实现

    def create_order(self, user_id: str, products: list) -> str:
        """创建订单前先验证用户存在性"""
        if not self.user_service.get_user(user_id):
            raise ValueError(f"用户{user_id}不存在")
        # 实现创建订单逻辑...
        return "order_456"
  1. 开放 - 封闭原则(Open-Closed Principle)

定义:对扩展开放(新增功能通过新增代码实现),对修改封闭(不改动已有代码)。

工程价值:避免修改旧代码引入新 bug,保障系统稳定性。

实践方式:通过抽象基类定义接口,新增功能时创建子类实现接口,而非修改基类。

from abc import ABC, abstractmethod

# 抽象基类:定义支付接口(对修改封闭)
class PaymentProvider(ABC):
    @abstractmethod
    def pay(self, amount: float) -> dict:
        """支付接口:返回支付结果(状态、交易号)"""
        pass

# 扩展实现:支付宝(对扩展开放)
class AlipayProvider(PaymentProvider):
    def pay(self, amount: float) -> dict:
        return {"status": "success", "txn_id": f"alipay_{id(self)}"}

# 扩展实现:微信支付(对扩展开放)
class WechatPayProvider(PaymentProvider):
    def pay(self, amount: float) -> dict:
        return {"status": "success", "txn_id": f"wechat_{id(self)}"}

# 业务逻辑:依赖抽象接口,无需修改即可支持新支付方式
class PaymentService:
    def __init__(self, provider: PaymentProvider):
        self.provider = provider

    def process_payment(self, amount: float) -> str:
        result = self.provider.pay(amount)
        return f"支付成功,交易号:{result['txn_id']}"
  1. 依赖倒置原则(Dependency Inversion Principle)

定义:高层模块(业务逻辑)依赖抽象接口,而非具体实现;抽象不依赖细节,细节依赖抽象。

工程价值:降低模块间耦合,使高层模块更稳定(不随底层实现变化而变化)。

反例OrderProcessor直接依赖Alipay类,若更换为微信支付,需修改OrderProcessor代码。

正例:依赖抽象的PaymentProvider接口,支持任意支付方式:

# 高层模块:订单处理器(依赖抽象接口)
class OrderProcessor:
    def __init__(self, payment_provider: PaymentProvider):
        # 依赖抽象,而非Alipay/WechatPay等具体实现
        self.payment_provider = payment_provider

    def process(self, order_id: str, amount: float) -> None:
        # 调用抽象方法,不关心具体支付方式
        result = self.payment_provider.pay(amount)
        print(f"订单{order_id}支付结果:{result['status']}")

# 底层实现:可替换的支付方式(依赖抽象接口)
class UnionPayProvider(PaymentProvider):
    def pay(self, amount: float) -> dict:
        return {"status": "success", "txn_id": f"unionpay_{id(self)}"}

# 使用:更换支付方式时,无需修改OrderProcessor
processor = OrderProcessor(UnionPayProvider())
processor.process("ORD123", 200.0)  # 输出:订单ORD123支付结果:success
  1. 里氏替换原则(Liskov Substitution Principle)

定义:子类对象必须能替换父类对象,且不改变程序的正确性(即子类应遵守父类的接口约定)。

工程价值:确保继承关系的合理性,避免多态场景下的逻辑异常。

反例:父类Bird定义fly()方法,子类Penguin继承后重写fly()但抛出异常(企鹅不会飞),导致def fly_bird(bird: Bird)函数在传入Penguin时出错。

正例:合理设计继承层次,子类严格遵守父类接口约定:

class PaymentProvider(ABC):
    @abstractmethod
    def pay(self, amount: float) -> dict:
        """支付接口:amount必须>0,返回包含status的字典"""
        if amount <= 0:
            raise ValueError("支付金额必须>0")  # 父类定义参数校验

class AlipayProvider(PaymentProvider):
    def pay(self, amount: float) -> dict:
        # 子类严格遵守父类约定:先调用父类校验,再实现自己的逻辑
        super().pay(amount)  # 复用父类的参数校验
        return {"status": "success", "txn_id": f"alipay_{id(self)}"}

# 里氏替换:子类可无缝替换父类
def test_payment(provider: PaymentProvider):
    try:
        provider.pay(-100)  # 测试无效金额
    except ValueError as e:
        print(f"正确捕获异常:{e}")

test_payment(AlipayProvider())  # 输出:正确捕获异常:支付金额必须>0(子类遵守父类约定)
  1. 接口隔离原则(Interface Segregation Principle)

定义:客户端不应依赖其不需要的接口(将大接口拆分为小接口,按需实现)。

工程价值:避免类被迫实现无关方法,减少代码冗余与耦合。

反例:一个庞大的DataProcessor接口包含read()write()parse()compress()等方法,导致仅需读取功能的FileReader类也必须实现write()compress()等无关方法(空实现或抛出异常)。

正例:拆分接口为专用接口,类按需实现:

# 拆分后的专用接口
class Reader(ABC):
    @abstractmethod
    def read(self) -> str: ...

class Writer(ABC):
    @abstractmethod
    def write(self, content: str) -> None: ...

class Parser(ABC):
    @abstractmethod
    def parse(self, data: str) -> dict: ...

# 仅实现需要的接口
class FileReader(Reader):
    def read(self) -> str:
        return "文件内容"  # 无需实现write/parse

class DataProcessor(Reader, Parser):
    # 同时实现读取和解析接口
    def read(self) -> str:
        return "原始数据"
    def parse(self, data: str) -> dict:
        return {"parsed": data}

7.5 面向对象设计实战:简易电商系统

以一个简化的电商系统为例,综合应用本章所学知识,展示面向对象设计的完整流程。

7.5.1 需求分析

设计一个支持用户注册、商品管理、订单创建与支付的简易电商系统,核心实体包括:用户、商品、订单、支付方式。主要功能需求:

  1. 用户管理:注册用户、查询用户信息
  2. 商品管理:添加商品、管理库存
  3. 订单处理:创建订单、支付订单
  4. 支付支持:支持多种支付方式(余额支付、支付宝等)

7.5.2 类结构设计

基于需求分析,设计以下类结构:

  1. 核心实体类User(用户)、Product(商品)、Order(订单)
  2. 业务服务类UserService(用户管理)、ProductService(商品管理)、OrderService(订单处理)
  3. 支付接口PaymentProvider(支付方式基类)及子类(如BalancePayAlipay

7.5.3 代码实现

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Dict, Optional


# 1. 数据模型类(使用dataclass简化属性定义)
@dataclass
class User:
    """用户数据模型"""
    user_id: str
    name: str
    balance: float = 0.0  # 账户余额

@dataclass
class Product:
    """商品数据模型"""
    product_id: str
    name: str
    price: float
    stock: int  # 库存


# 2. 支付接口(策略模式)
class PaymentProvider(ABC):
    """支付方式基类(抽象策略)"""
    @abstractmethod
    def pay(self, user: User, amount: float) -> Dict[str, str]:
        """支付接口:返回支付结果"""
        pass

class BalancePay(PaymentProvider):
    """余额支付(具体策略)"""
    def pay(self, user: User, amount: float) -> Dict[str, str]:
        if user.balance < amount:
            return {"status": "fail", "reason": "余额不足"}
        user.balance -= amount
        return {"status": "success", "txn_id": f"balance_{id(self)}"}

class Alipay(PaymentProvider):
    """支付宝支付(具体策略)"""
    def pay(self, user: User, amount: float) -> Dict[str, str]:
        # 实际场景会调用支付宝API,此处简化
        return {"status": "success", "txn_id": f"alipay_{id(self)}"}


# 3. 业务服务类(单一职责原则)
class UserService:
    """用户服务:处理用户注册、查询"""
    def __init__(self):
        self._users: Dict[str, User] = {}  # 模拟数据库存储

    def register(self, user_id: str, name: str) -> User:
        """注册新用户"""
        if user_id in self._users:
            raise ValueError(f"用户{user_id}已存在")
        user = User(user_id, name)
        self._users[user_id] = user
        return user

    def get_user(self, user_id: str) -> Optional[User]:
        """查询用户"""
        return self._users.get(user_id)


class ProductService:
    """商品服务:处理商品上架、库存管理"""
    def __init__(self):
        self._products: Dict[str, Product] = {}  # 模拟数据库存储

    def add_product(self, product: Product) -> None:
        """添加商品"""
        if product.product_id in self._products:
            raise ValueError(f"商品{product.product_id}已存在")
        self._products[product.product_id] = product

    def reduce_stock(self, product_id: str, quantity: int) -> bool:
        """减少库存"""
        product = self._products.get(product_id)
        if not product or product.stock < quantity:
            return False
        product.stock -= quantity
        return True


class OrderService:
    """订单服务:处理订单创建、支付"""
    def __init__(self, user_service: UserService, product_service: ProductService):
        self._user_service = user_service
        self._product_service = product_service
        self._orders: Dict[str, Dict] = {}  # 模拟订单存储

    def create_order(self, user_id: str, items: List[Dict[str, int]]) -> str:
        """创建订单
        items格式:[{"product_id": "p1", "quantity": 2}, ...]
        """
        # 1. 验证用户
        user = self._user_service.get_user(user_id)
        if not user:
            raise ValueError(f"用户{user_id}不存在")

        # 2. 验证商品和库存
        total_amount = 0.0
        for item in items:
            product = self._products.get(item["product_id"])
            if not product:
                raise ValueError(f"商品{item['product_id']}不存在")
            if product.stock < item["quantity"]:
                raise ValueError(f"商品{item['product_id']}库存不足")
            total_amount += product.price * item["quantity"]

        # 3. 创建订单
        order_id = f"order_{len(self._orders) + 1}"
        self._orders[order_id] = {
            "user_id": user_id,
            "items": items,
            "total_amount": total_amount,
            "status": "pending"  # 待支付
        }
        return order_id

    def pay_order(self, order_id: str, payment_provider: PaymentProvider) -> bool:
        """支付订单"""
        order = self._orders.get(order_id)
        if not order or order["status"] != "pending":
            return False

        user = self._user_service.get_user(order["user_id"])
        result = payment_provider.pay(user, order["total_amount"])
        if result["status"] == "success":
            order["status"] = "paid"
            # 支付成功后扣减库存
            for item in order["items"]:
                self._product_service.reduce_stock(item["product_id"], item["quantity"])
            return True
        return False


# 4. 系统使用示例
if __name__ == "__main__":
    # 初始化服务
    user_service = UserService()
    product_service = ProductService()
    order_service = OrderService(user_service, product_service)

    # 注册用户
    user = user_service.register("u1001", "张三")
    user.balance = 1000  # 充值余额

    # 添加商品
    product_service.add_product(Product("p001", "Python编程书籍", 59.0, 100))
    product_service.add_product(Product("p002", "机械键盘", 299.0, 50))

    # 创建订单
    order_id = order_service.create_order(
        "u1001",
        [{"product_id": "p001", "quantity": 1}, {"product_id": "p002", "quantity": 1}]
    )
    print(f"创建订单:{order_id}")

    # 支付订单(使用余额支付)
    success = order_service.pay_order(order_id, BalancePay())
    print(f"支付结果:{'成功' if success else '失败'}")  # 输出:成功
    print(f"用户剩余余额:{user.balance}")  # 输出:642.0(1000 - 59 - 299)