2.2.1 面向对象编程

这一节介绍 Python 中组织复杂代码的一种重要方式。面向对象不是一开始就必须精通,但理解类、对象、属性和方法,能帮助你读懂后面的模型类、数据集类、API 服务类和第三方库源码。
- 理解面向对象编程(OOP)的基本思想
- 掌握类和对象的定义与使用
- 理解属性和方法
- 掌握继承、封装的基本用法
- 了解常用的魔术方法
为什么需要面向对象?
Section titled “为什么需要面向对象?”假设你在为 AI 作品集应用开发一个小型项目追踪器,需要记录每个功能的负责人和工作记录:
# 用变量和字典的方式feature1_name = "登录 API"feature1_owner = "Mina"feature1_hours = [2, 5, 1]
feature2_name = "RAG 演示"feature2_owner = "Kai"feature2_hours = [3, 4, 2]
# 或者用字典feature1 = {"name": "登录 API", "owner": "Mina", "hours": [2, 5, 1]}feature2 = {"name": "RAG 演示", "owner": "Kai", "hours": [3, 4, 2]}
# 计算总工时的函数def total_hours(feature): return sum(feature["hours"])这样写有几个问题:
- 数据和操作是分离的(功能数据在字典里,计算函数在外面)
- 没有约束(谁都可以往字典里加奇怪的键,或者删掉必要的键)
- 当功能的属性越来越多时,代码会越来越乱
面向对象编程的思路是:把数据和操作打包在一起,形成一个”对象”。
class FeatureTask: def __init__(self, name, owner, hours): self.name = name self.owner = owner self.hours = hours
def total_hours(self): return sum(self.hours)
# 创建功能任务对象task1 = FeatureTask("登录 API", "Mina", [2, 5, 1])task2 = FeatureTask("RAG 演示", "Kai", [3, 4, 2])
# 数据和操作绑在一起,使用起来更自然print(f"{task1.name} 总工时: {task1.total_hours():.1f}")print(f"{task2.name} 总工时: {task2.total_hours():.1f}")类和对象的基本概念
Section titled “类和对象的基本概念”用一个生活中的类比:
- 类(Class) = 蓝图/模板。比如”手机”是一个概念/类别
- 对象(Object/Instance) = 用蓝图造出来的实体。比如”你手里的那台 iPhone 15”
类:FeatureTask(功能任务的模板) └── 属性:name, owner, hours └── 方法:total_hours(), is_over_budget()
对象(实例): └── task1 = FeatureTask("登录 API", "Mina", [2, 5, 1]) └── task2 = FeatureTask("RAG 演示", "Kai", [3, 4, 2])class Dog: """一只狗"""
def __init__(self, name, breed): """初始化方法,创建对象时自动调用""" self.name = name # 实例属性 self.breed = breed # 实例属性
def bark(self): """方法:狗叫""" print(f"{self.name} 说: 汪汪汪!")
def info(self): """方法:显示信息""" print(f"名字: {self.name}, 品种: {self.breed}")
# 创建对象(实例化)my_dog = Dog("旺财", "金毛")your_dog = Dog("小黑", "拉布拉多")
# 访问属性print(my_dog.name) # 旺财print(your_dog.breed) # 拉布拉多
# 调用方法my_dog.bark() # 旺财 说: 汪汪汪!your_dog.info() # 名字: 小黑, 品种: 拉布拉多1. __init__ 方法(构造方法)
__init__ 在你创建对象时自动调用,用来初始化对象的属性。
my_dog = Dog("旺财", "金毛")# Python 自动做了这些事:# 1. 创建一个新的 Dog 对象# 2. 调用 __init__(self, "旺财", "金毛")# 3. self.name = "旺财"# 4. self.breed = "金毛"# 5. 返回这个对象给 my_dog2. self 是什么?
self 代表对象自己。当你调用 my_dog.bark() 时,Python 会自动把 my_dog 作为 self 传给 bark 方法。
my_dog.bark()# 等价于Dog.bark(my_dog)所以 self.name 就是”这个对象的 name”。
实例属性 vs 类属性
Section titled “实例属性 vs 类属性”class FeatureTask: # 类属性:所有实例共享 project = "AI 作品集" task_count = 0
def __init__(self, name, owner): # 实例属性:每个实例独有 self.name = name self.owner = owner FeatureTask.task_count += 1 # 每创建一个任务,计数加 1
t1 = FeatureTask("登录 API", "Mina")t2 = FeatureTask("RAG 演示", "Kai")
# 类属性通过类名或实例都能访问print(FeatureTask.project) # AI 作品集print(t1.project) # AI 作品集print(FeatureTask.task_count) # 2
# 实例属性只属于各自的实例print(t1.name) # 登录 APIprint(t2.owner) # Kaiclass Circle: def __init__(self, radius): self.radius = radius
def area(self): """计算面积""" return 3.14159 * self.radius ** 2
def perimeter(self): """计算周长""" return 2 * 3.14159 * self.radius
def scale(self, factor): """缩放半径""" self.radius *= factor # 修改属性
c = Circle(5)print(f"面积: {c.area():.2f}") # 78.54print(f"周长: {c.perimeter():.2f}") # 31.42
c.scale(2) # 半径变为 10print(f"缩放后面积: {c.area():.2f}") # 314.16魔术方法(双下划线方法)
Section titled “魔术方法(双下划线方法)”Python 中以 __ 开头和结尾的方法叫魔术方法(Magic Methods),它们让你的类可以像内置类型一样使用。
__str__:定义 print 的输出
Section titled “__str__:定义 print 的输出”class FeatureTask: def __init__(self, name, owner): self.name = name self.owner = owner
def __str__(self): return f"FeatureTask({self.name}, owner={self.owner})"
task = FeatureTask("登录 API", "Mina")print(task) # FeatureTask(登录 API, owner=Mina)# 如果没有 __str__,print 会输出 <__main__.FeatureTask object at 0x...>__repr__:定义开发者看到的表示
Section titled “__repr__:定义开发者看到的表示”class FeatureTask: def __init__(self, name, owner): self.name = name self.owner = owner
def __repr__(self): return f"FeatureTask('{self.name}', '{self.owner}')"
task = FeatureTask("登录 API", "Mina")print(repr(task)) # FeatureTask('登录 API', 'Mina')# 在交互模式中直接输入 task 也会显示这个__len__:定义 len() 的行为
Section titled “__len__:定义 len() 的行为”class Playlist: def __init__(self, name, songs): self.name = name self.songs = songs
def __len__(self): return len(self.songs)
my_playlist = Playlist("学习音乐", ["歌曲A", "歌曲B", "歌曲C"])print(len(my_playlist)) # 3__eq__:定义 == 的行为
Section titled “__eq__:定义 == 的行为”class Point: def __init__(self, x, y): self.x = x self.y = y
def __eq__(self, other): return self.x == other.x and self.y == other.y
p1 = Point(3, 4)p2 = Point(3, 4)p3 = Point(1, 2)
print(p1 == p2) # Trueprint(p1 == p3) # False继承让你可以基于已有的类创建新类,复用代码。
# 父类(基类)class Animal: def __init__(self, name, age): self.name = name self.age = age
def speak(self): print(f"{self.name} 发出了声音")
def info(self): print(f"{self.name}, {self.age}岁")
# 子类(派生类)class Dog(Animal): def __init__(self, name, age, breed): super().__init__(name, age) # 调用父类的 __init__ self.breed = breed
def speak(self): # 重写父类的方法 print(f"{self.name} 说: 汪汪汪!")
def fetch(self): # 子类独有的方法 print(f"{self.name} 把球捡回来了!")
class Cat(Animal): def speak(self): # 重写父类的方法 print(f"{self.name} 说: 喵喵喵~")
# 使用dog = Dog("旺财", 3, "金毛")cat = Cat("咪咪", 2)
dog.info() # 旺财, 3岁(继承自 Animal)dog.speak() # 旺财 说: 汪汪汪!(Dog 自己的实现)dog.fetch() # 旺财 把球捡回来了!(Dog 独有的)
cat.info() # 咪咪, 2岁cat.speak() # 咪咪 说: 喵喵喵~super() 的作用
Section titled “super() 的作用”super() 用来调用父类的方法,最常见的用法是在 __init__ 中:
class Animal: def __init__(self, name): self.name = name
class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # 让父类帮我初始化 name self.breed = breed # 自己初始化 breedisinstance() 检查类型
Section titled “isinstance() 检查类型”dog = Dog("旺财", 3, "金毛")
print(isinstance(dog, Dog)) # True —— 是 Dogprint(isinstance(dog, Animal)) # True —— 也是 Animal(因为继承)print(isinstance(dog, Cat)) # False —— 不是 Cat封装的思想是:隐藏内部细节,只暴露必要的接口。
私有属性(约定)
Section titled “私有属性(约定)”Python 没有真正的私有属性,但有命名约定:
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self._balance = balance # 单下划线:约定为"内部使用"
def deposit(self, amount): if amount > 0: self._balance += amount print(f"存入 {amount} 元,余额: {self._balance}")
def withdraw(self, amount): if 0 < amount <= self._balance: self._balance -= amount print(f"取出 {amount} 元,余额: {self._balance}") else: print("余额不足!")
def get_balance(self): return self._balance
account = BankAccount("作品集账户", 1000)account.deposit(500) # 存入 500 元,余额: 1500account.withdraw(200) # 取出 200 元,余额: 1300print(account.get_balance()) # 1300
# 虽然技术上可以直接访问 _balance,但这不是推荐的做法# print(account._balance) # 能用,但不应该这么做| 命名约定 | 含义 | 示例 |
|---|---|---|
name | 公开属性 | self.name |
_name | 内部使用(约定) | self._balance |
__name | 名称改写(强制隐藏) | self.__secret |
综合案例:AI 模型管理器
Section titled “综合案例:AI 模型管理器”class AIModel: """AI 模型基类""" model_count = 0
def __init__(self, name, version="1.0"): self.name = name self.version = version self.is_trained = False self._accuracy = 0.0 self._history = [] AIModel.model_count += 1
def train(self, epochs=10): """训练模型(模拟)""" import random print(f"开始训练 {self.name} v{self.version}...") for epoch in range(1, epochs + 1): acc = min(0.5 + epoch * 0.05 + random.uniform(-0.02, 0.02), 1.0) self._history.append(acc) if epoch % 5 == 0 or epoch == epochs: print(f" Epoch {epoch}/{epochs} - Accuracy: {acc:.2%}") self._accuracy = self._history[-1] self.is_trained = True print(f"训练完成!最终准确率: {self._accuracy:.2%}")
def predict(self, data): """预测""" if not self.is_trained: print("错误:模型还没有训练!") return None print(f"{self.name} 正在预测 {len(data)} 条数据...") return [f"预测结果_{i}" for i in range(len(data))]
def __str__(self): status = "已训练" if self.is_trained else "未训练" return f"Model({self.name} v{self.version}, {status}, acc={self._accuracy:.2%})"
class ImageClassifier(AIModel): """图像分类模型""" def __init__(self, name, version="1.0", num_classes=10): super().__init__(name, version) self.num_classes = num_classes
def predict(self, images): if not self.is_trained: print("错误:模型还没有训练!") return None print(f"正在对 {len(images)} 张图片进行分类({self.num_classes} 个类别)...") import random return [random.randint(0, self.num_classes - 1) for _ in images]
# 使用model = ImageClassifier("ResNet-50", "2.0", num_classes=100)print(model) # Model(ResNet-50 v2.0, 未训练, acc=0.00%)
model.train(epochs=10)predictions = model.predict(["img1.jpg", "img2.jpg", "img3.jpg"])print(f"预测类别: {predictions}")print(model)print(f"当前模型总数: {AIModel.model_count}")练习 1:图书管理
Section titled “练习 1:图书管理”创建一个 Book 类:
class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages self.current_page = 0
def read(self, pages): self.current_page = min(self.current_page + pages, self.pages)
def progress(self): return self.current_page / self.pages * 100
def __str__(self): return f"{self.title},作者:{self.author},进度:{self.current_page}/{self.pages} 页"
# 测试book = Book("Python 入门", "课程团队", 300)book.read(50)print(f"{book.progress():.1f}%") # 16.7%print(book)练习 2:简单的推理任务队列
Section titled “练习 2:简单的推理任务队列”class InferenceJob: def __init__(self, name, tokens): self.name = name self.tokens = tokens
class JobQueue: def __init__(self): self.jobs = {}
def add(self, job, replicas=1): self.jobs[job.name] = self.jobs.get(job.name, [job, 0]) self.jobs[job.name][1] += replicas
def remove(self, job_name): self.jobs.pop(job_name, None)
def estimated_tokens(self): return sum(job.tokens * replicas for job, replicas in self.jobs.values())
def __str__(self): lines = [f"{job.name} x {replicas}" for job, replicas in self.jobs.values()] return "\n".join(lines) or "任务队列为空"
queue = JobQueue()queue.add(InferenceJob("embed-docs", 800), 2)queue.add(InferenceJob("answer-query", 1200), 1)print(queue)print(f"预估 token 数:{queue.estimated_tokens()}")练习 3:动物园
Section titled “练习 3:动物园”用继承实现:
class Animal: def __init__(self, name, age): self.name = name self.age = age
def speak(self): return "..."
class Dog(Animal): def speak(self): return "汪汪"
class Cat(Animal): def speak(self): return "喵喵"
class Duck(Animal): def speak(self): return "嘎嘎"
animals = [Dog("小黑", 3), Cat("咪咪", 2), Duck("小鸭", 1)]for animal in animals: print(f"{animal.name}: {animal.speak()}")参考实现与讲解
Book应该把当前页数作为内部状态保存,并且用progress()计算阅读进度。读完 50/300 页后,示例输出应当接近16.7%,对象字符串里应显示50/300。ShoppingCart应该把商品对象和数量一起保存,这样total()才能正确相乘。remove()在商品不存在时应保持安全,__str__()在购物车为空时应返回清楚的提示。Animal提供共享字段和占位版speak(),子类只覆盖自己的叫声。循环输出每个动物的名字和叫声,就能证明继承与多态是正常工作的。
学完这一页,至少保留这张证据卡:
- 模式
- 类、异常、文件 IO、函数式流水线、生成器或类型提示
- 代码产物
- 最小可运行示例和一个真实使用场景
- 输出
- 打印的对象状态、捕获的错误、保存的文件、yield 的值,或类型检查备注
- 失败检查
- 隐藏变异、吞掉异常、文件路径问题、懒迭代器混淆或误导性标注
- 期望产出
- 带调试说明的小型高级 Python 示例
| 概念 | 说明 | 语法 |
|---|---|---|
| 类 | 对象的模板/蓝图 | class MyClass: |
| 对象 | 类的实例 | obj = MyClass() |
__init__ | 构造方法,初始化属性 | def __init__(self): |
| self | 指向当前对象自身 | self.name = name |
| 继承 | 子类复用父类的代码 | class Dog(Animal): |
| super() | 调用父类的方法 | super().__init__() |
| 魔术方法 | 自定义对象行为 | __str__, __len__, __eq__ |