面向对象编程

学习时长:120分钟 练习题:5个

面向对象编程简介

面向对象编程(Object-Oriented Programming,简称OOP)是一种程序设计范式,它将程序中的数据和操作数据的方法组织成对象,通过对象之间的交互来完成程序功能。

为什么要学习面向对象编程?

  • 更好的代码组织方式,提高代码的可维护性
  • 代码重用性强,减少重复代码
  • 更接近人类思维方式,易于理解和使用
  • 适合开发大型复杂的程序

类与对象

类的定义

类是对象的模板,它定义了对象的属性和方法。在Python中使用class关键字定义类:

class Student:
    """学生类"""
    
    def __init__(self, name, age):
        self.name = name  # 实例属性
        self.age = age
        
    def introduce(self):  # 实例方法
        return f"我叫{self.name},今年{self.age}岁"

对象的创建

对象是类的实例,通过类名后跟括号的方式创建:

# 创建Student类的实例
student1 = Student("张三", 18)
student2 = Student("李四", 19)

# 调用对象的方法
print(student1.introduce())  # 输出:我叫张三,今年18岁
print(student2.introduce())  # 输出:我叫李四,今年19岁

属性与方法

实例属性

实例属性是属于对象的变量,每个对象都有自己的一份独立的实例属性:

class Dog:
    def __init__(self, name):
        self.name = name    # 实例属性
        self.tricks = []    # 另一个实例属性

    def add_trick(self, trick):
        self.tricks.append(trick)

# 创建两个Dog实例
dog1 = Dog("旺财")
dog2 = Dog("来福")

# 给狗狗教不同的技能
dog1.add_trick("握手")
dog2.add_trick("打滚")

print(dog1.tricks)  # ['握手']
print(dog2.tricks)  # ['打滚']

类属性

类属性是属于类的变量,该类的所有实例共享同一个类属性:

class Student:
    school = "Python学习网"  # 类属性
    
    def __init__(self, name):
        self.name = name    # 实例属性

# 创建实例
student1 = Student("张三")
student2 = Student("李四")

# 访问类属性
print(Student.school)      # Python学习网
print(student1.school)     # Python学习网
print(student2.school)     # Python学习网

# 修改类属性
Student.school = "新Python学习网"
print(student1.school)     # 新Python学习网
print(student2.school)     # 新Python学习网

继承与多态

继承基础

继承允许我们基于一个类创建新类,新类继承了原有类的属性和方法:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return f"{self.name}说:汪汪!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}说:喵喵!"

# 创建实例
dog = Dog("旺财")
cat = Cat("咪咪")

print(dog.speak())  # 旺财说:汪汪!
print(cat.speak())  # 咪咪说:喵喵!

方法重写

子类可以重写(覆盖)父类的方法,以实现自己的特定行为。如果需要调用父类的方法,可以使用super()函数:

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    def start_engine(self):
        return f"{self.brand} {self.model}的发动机启动了"
    
    def show_info(self):
        return f"这是一辆{self.brand} {self.model}"

class ElectricCar(Vehicle):
    def __init__(self, brand, model, battery_capacity):
        # 调用父类的__init__方法
        super().__init__(brand, model)
        self.battery_capacity = battery_capacity
    
    def start_engine(self):
        # 完全重写父类的方法
        return f"{self.brand} {self.model}的电机启动了,电池容量{self.battery_capacity}kWh"
    
    def show_info(self):
        # 在父类方法基础上添加新功能
        basic_info = super().show_info()
        return f"{basic_info},使用电力驱动"

# 创建实例
car = Vehicle("丰田", "凯美瑞")
tesla = ElectricCar("特斯拉", "Model 3", 75)

# 调用方法
print(car.start_engine())    # 丰田 凯美瑞的发动机启动了
print(tesla.start_engine())  # 特斯拉 Model 3的电机启动了,电池容量75kWh
print(tesla.show_info())     # 这是一辆特斯拉 Model 3,使用电力驱动

方法重写的三种常见方式

  • 完全重写:子类完全覆盖父类的方法实现
  • 部分重写:调用父类方法,并添加新功能
  • 选择性重写:根据条件决定是否调用父类方法

多态性

多态性允许我们以统一的方式处理不同类型的对象,只要这些对象都是某个共同基类的子类:

class Shape:
    def area(self):
        pass
    
    def describe(self):
        return f"这是一个{self.__class__.__name__}"

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

def print_shape_info(shape):
    """多态函数:接受任何Shape类型的对象"""
    print(shape.describe())
    print(f"面积是: {shape.area()}")

# 创建不同的形状
shapes = [
    Circle(5),
    Rectangle(4, 6),
    Circle(3)
]

# 统一处理不同类型的对象
for shape in shapes:
    print_shape_info(shape)
    print("---")

Python的鸭子类型

Python支持"鸭子类型":如果一个对象实现了某个方法,我们就可以使用它, 而不需要关心它的具体类型。这提供了更灵活的多态性:

class Duck:
    def speak(self):
        return "嘎嘎!"

class Cat:
    def speak(self):
        return "喵喵!"

class Person:
    def speak(self):
        return "你好!"

def make_speak(thing):
    # 不关心对象类型,只要有speak方法就可以调用
    print(thing.speak())

# 使用鸭子类型
things = [Duck(), Cat(), Person()]
for thing in things:
    make_speak(thing)

注意事项

  • 确保子类方法的参数与父类方法兼容
  • 重写方法时保持接口的一致性
  • 适当使用抽象基类来强制接口规范

封装与抽象

私有属性

在Python中,通过在属性名前加双下划线__来创建私有属性,这样的属性只能在类的内部访问:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner          # 公有属性
        self.__balance = balance    # 私有属性
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
    
    def get_balance(self):
        return self.__balance

# 创建账户
account = BankAccount("张三", 1000)

# 正确的访问方式
print(account.owner)           # 可以直接访问公有属性
account.deposit(500)          # 通过方法访问私有属性
print(account.get_balance())  # 输出:1500

# 错误的访问方式
# print(account.__balance)    # 这会引发AttributeError错误

名称改写

Python中的私有属性实际上是通过名称改写(name mangling)实现的。 双下划线开头的属性会被改写为_类名__属性名的形式。这不是真正的私有, 但这种约定俗成的方式可以有效防止属性被意外访问或修改。

属性装饰器

使用@property装饰器可以将方法转换为属性,实现更优雅的属性访问和控制:

class Temperature:
    def __init__(self, celsius):
        self.__celsius = celsius
    
    @property
    def celsius(self):
        """获取摄氏温度"""
        return self.__celsius
    
    @celsius.setter
    def celsius(self, value):
        """设置摄氏温度"""
        if value < -273.15:  # 验证温度不低于绝对零度
            raise ValueError("温度不能低于绝对零度")
        self.__celsius = value
    
    @property
    def fahrenheit(self):
        """获取华氏温度"""
        return self.__celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """设置华氏温度"""
        self.celsius = (value - 32) * 5/9

# 使用示例
temp = Temperature(25)
print(temp.celsius)     # 25
print(temp.fahrenheit)  # 77.0

temp.celsius = 30       # 使用setter设置温度
print(temp.fahrenheit)  # 86.0

# temp.celsius = -300   # 这会引发ValueError

抽象基类(ABC)

抽象基类用于定义接口规范,它可以强制子类实现特定的方法。Python通过abc模块提供抽象基类支持:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """计算面积的抽象方法"""
        pass
    
    @abstractmethod
    def perimeter(self):
        """计算周长的抽象方法"""
        pass
    
    def describe(self):
        """普通方法(非抽象方法)"""
        return f"这是一个{self.__class__.__name__},面积为{self.area()}"

# 尝试实例化抽象类会引发错误
# shape = Shape()  # TypeError: Can't instantiate abstract class Shape

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):  # 必须实现area方法
        return 3.14 * self.radius ** 2
    
    def perimeter(self):  # 必须实现perimeter方法
        return 2 * 3.14 * self.radius

# 正确使用示例
circle = Circle(5)
print(circle.area())       # 78.5
print(circle.describe())   # 这是一个Circle,面积为78.5

抽象基类的作用

  • 定义接口规范,确保子类实现必要的方法
  • 提供代码复用的基础架构
  • 支持类型检查和验证

抽象基类还可以包含抽象属性和具体实现:

from abc import ABC, abstractmethod, abstractproperty

class Vehicle(ABC):
    @abstractmethod
    def start_engine(self):
        """启动引擎"""
        pass
    
    @abstractproperty
    def fuel_type(self):
        """燃料类型"""
        pass
    
    def stop_engine(self):
        """关闭引擎(具体实现)"""
        return "引擎已关闭"

class ElectricCar(Vehicle):
    @property
    def fuel_type(self):
        return "电力"
    
    def start_engine(self):
        return "电机启动"

class GasCar(Vehicle):
    @property
    def fuel_type(self):
        return "汽油"
    
    def start_engine(self):
        return "汽油发动机启动"

# 使用示例
tesla = ElectricCar()
toyota = GasCar()

print(tesla.fuel_type)    # 电力
print(toyota.fuel_type)   # 汽油
print(tesla.start_engine())  # 电机启动
print(toyota.stop_engine())  # 引擎已关闭

注意事项

  • 抽象方法必须被子类实现,否则子类也会变成抽象类
  • 抽象基类可以包含具体方法的实现
  • 使用@abstractmethod而不是@abstractproperty(已弃用)

实际应用示例:数据库接口

from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        """连接数据库"""
        pass
    
    @abstractmethod
    def disconnect(self):
        """断开连接"""
        pass
    
    @abstractmethod
    def execute(self, query):
        """执行查询"""
        pass
    
    def transaction(self, queries):
        """事务处理(具体实现)"""
        try:
            for query in queries:
                self.execute(query)
            return True
        except Exception as e:
            print(f"事务失败: {e}")
            return False

class MySQLDatabase(Database):
    def connect(self):
        return "连接到MySQL数据库"
    
    def disconnect(self):
        return "断开MySQL连接"
    
    def execute(self, query):
        return f"在MySQL中执行: {query}"

class PostgreSQLDatabase(Database):
    def connect(self):
        return "连接到PostgreSQL数据库"
    
    def disconnect(self):
        return "断开PostgreSQL连接"
    
    def execute(self, query):
        return f"在PostgreSQL中执行: {query}"

# 使用示例
def process_data(database: Database):
    database.connect()
    database.execute("SELECT * FROM users")
    database.disconnect()

# 可以使用任何Database的子类
mysql_db = MySQLDatabase()
postgres_db = PostgreSQLDatabase()

process_data(mysql_db)
process_data(postgres_db)

魔术方法

__init__方法

__init__是最常用的魔术方法,用于初始化对象:

class Point:
    def __init__(self, x=0, y=0):
        """初始化方法,创建点的坐标"""
        self.x = x
        self.y = y

# 创建点对象
p1 = Point()        # 使用默认值 (0, 0)
p2 = Point(3, 4)    # 指定坐标 (3, 4)

__str__和__repr__

这两个方法用于对象的字符串表示:

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    
    def __str__(self):
        """返回对象的字符串表示,面向用户"""
        return f"点({self.x}, {self.y})"
    
    def __repr__(self):
        """返回对象的详细字符串表示,面向开发者"""
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
print(str(p))   # 输出:点(3, 4)
print(repr(p))  # 输出:Point(x=3, y=4)

其他魔术方法

Python提供了许多其他魔术方法来自定义对象的行为:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """实现向量加法:+运算符"""
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        """实现向量减法:-运算符"""
        return Vector(self.x - other.x, self.y - other.y)
    
    def __len__(self):
        """实现len()函数"""
        return int((self.x**2 + self.y**2)**0.5)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# 使用示例
v1 = Vector(2, 3)
v2 = Vector(3, 4)
v3 = v1 + v2       # 使用+运算符
print(v3)          # Vector(5, 7)
print(len(v1))     # 3 (向量长度的整数部分)

常用魔术方法

  • __eq__(self, other): 定义==运算符
  • __lt__(self, other): 定义<运算符
  • __getitem__(self, key): 定义索引访问
  • __call__(self, *args): 使对象可调用
  • __enter__和__exit__: 上下文管理器

练习与实践

难度说明

  • 基础练习:巩固基本概念
  • 进阶练习:综合应用多个概念
  • 挑战练习:需要深入思考和创新

练习1:创建图书类

创建一个Book类,包含以下要求:

  • 属性:书名、作者、价格、库存量
  • 方法:
    • 显示图书信息
    • 更新库存量
    • 计算打折后的价格

提示:使用property装饰器处理价格的获取和设置,确保价格不能为负数

参考代码结构:

class Book:
    def __init__(self, title, author, price, stock):
        self.title = title
        self.author = author
        self._price = price  # 使用下划线表示protected属性
        self.stock = stock
    
    @property
    def price(self):
        # 在这里实现价格的getter方法
        pass
    
    @price.setter
    def price(self, value):
        # 在这里实现价格的setter方法
        pass

练习2:银行账户系统

设计一个简单的银行账户系统,包含以下类:

  • Account(基类):基本账户操作
  • SavingAccount(储蓄账户):包含利息计算
  • CheckingAccount(支票账户):包含透支额度

提示:使用继承实现不同类型的账户,确保账户余额不会出现非法操作

测试用例:

# 创建账户
savings = SavingAccount("张三", 1000, 0.05)  # 年利率5%
checking = CheckingAccount("李四", 2000, 500)  # 透支额度500

# 测试存款和取款
savings.deposit(500)
assert savings.balance == 1500
checking.withdraw(2300)  # 允许透支

练习3:商品库存管理

实现一个商品库存管理系统,要求:

  • 创建Product基类和不同类型的商品子类(如Electronics、Clothing等)
  • 实现库存管理功能(入库、出库、库存查询)
  • 使用property装饰器管理商品属性
  • 实现商品搜索和分类功能

提示:考虑使用类方法和静态方法来实现一些通用功能

示例实现:

class Product:
    _all_products = []  # 类属性,存储所有商品
    
    def __init__(self, name, price, stock):
        self.name = name
        self._price = price
        self._stock = stock
        Product._all_products.append(self)
    
    @classmethod
    def search(cls, keyword):
        """搜索商品"""
        return [p for p in cls._all_products if keyword in p.name]
    
    @staticmethod
    def validate_price(price):
        """验证价格是否合法"""
        return price > 0

练习4:游戏角色系统

设计一个游戏角色系统,包含以下功能:

  • 创建Character基类和不同职业的子类(如Warrior、Mage、Archer)
  • 实现角色属性(生命值、魔法值、攻击力等)
  • 设计技能系统(每个职业有特殊技能)
  • 实现角色交互(攻击、防御、使用技能等)
  • 添加状态效果系统(如中毒、眩晕等)

提示:使用多重继承或混入类(Mixin)来实现状态效果系统

示例代码结构:

class Character:
    def __init__(self, name, health, mana, attack):
        self.name = name
        self._health = health
        self._mana = mana
        self._attack = attack
        self._effects = []  # 状态效果列表
    
    def use_skill(self, skill, target):
        if self._mana >= skill.mana_cost:
            self._mana -= skill.mana_cost
            return skill.execute(self, target)
        return "魔法值不足!"

class Warrior(Character):
    def __init__(self, name):
        super().__init__(name, health=100, mana=50, attack=15)
        self.skills = [
            Skill("冲锋", 10, self._charge),
            Skill("旋风斩", 20, self._whirlwind)
        ]
    
    def _charge(self, target):
        # 实现冲锋技能
        pass

练习5:自定义集合类

实现一个自定义的集合类,要求:

  • 支持基本的集合操作(添加、删除、查找等)
  • 实现迭代器协议(__iter__和__next__)
  • 支持运算符重载(并集+、交集&、差集-)
  • 实现长度计算和成员检测
  • 支持切片操作

提示:需要实现多个魔术方法来支持这些功能

测试代码:

# 创建自定义集合
s1 = MySet([1, 2, 3, 4])
s2 = MySet([3, 4, 5, 6])

# 测试运算符重载
s3 = s1 + s2  # 并集
s4 = s1 & s2  # 交集
s5 = s1 - s2  # 差集

# 测试迭代和切片
for item in s1:
    print(item)
print(s1[1:3])  # 切片访问