Python 面向对象与高级特性学习笔记(下篇:OOP ~ 文件 ~ 异常 ~ 综合项目)
涵盖面向对象编程(封装、继承、多态)、异常处理、文件操作、JSON 序列化、综合项目实战。
目录
- • 第六章:面向对象编程基础(33-34)
- • 第七章:异常处理(35)
- • 第八章:文件操作(35-36)
- • 第九章:面向对象三大特性
- • 第十章:综合项目 — 图书管理系统(41)
- • 附录:面向对象核心概念速查
第六章:面向对象编程基础(33-34)
6.1 类的定义与实例化
核心思想: 万物皆对象。类是创建对象的模板,对象是类的具体实例。
定义语法:
# 定义类
class Car:
pass
# 创建对象(实例化)
c1 = Car()
# 动态添加属性
c1.brand = "BMW"
c1.name = "X5"
c1.price = 500000
# 查看对象的所有属性
print(c1.__dict__) # {'brand': 'BMW', 'name': 'X5', 'price': 500000}
类名命名规范 — 大驼峰命名法:
| 正确 | 错误 |
|---|---|
MyClass | my_class |
StudentInfo | userinfo |
HttpServer | http_server |
6.2 构造方法 __init__ 与 self
推荐写法 — 使用 __init__ 初始化属性:
class Car:
def __init__(self, brand, name, price):
self.brand = brand
self.name = name
self.price = price
# 创建对象时直接传入属性值
c1 = Car("BMW", "X5", 500000)
print(c1.brand) # BMW
__init__ 方法详解:
| 要点 | 说明 |
|---|---|
| 触发时机 | 使用 类名() 创建对象时自动调用 |
| 作用 | 给新对象赋予初始属性 |
| 命名特征 | 前后各两个下划线 __init__ |
self 参数详解:
| 要点 | 说明 |
|---|---|
| 含义 | 代表当前正在操作的那个对象实例 |
| 核心作用 | 区分不同对象的数据(p1 的数据存在 p1 的内存空间) |
| 调用方式 | 在类内部必须通过 self.属性名 或 self.方法名() 访问 |
| 自动传入 | 调用方法时不需要手动传 self,Python 自动传入 |
6.3 实例方法
实例方法是定义在类中的函数,用于描述对象的行为。
语法:
class Car:
def __init__(self, brand, name, price):
self.brand = brand
self.name = name
self.price = price
def running(self):
print(f"{self.brand} {self.name} 正在高速行驶...")
def total_cost(self, discount, rate):
return self.price * discount + self.price * rate
c1 = Car("BMW", "X5", 500000)
c1.running() # BMW X5 正在高速行驶...
final_price = c1.total_cost(0.9, 0.1) # 提车总价
调用规则:
- • 定义时:第一个参数必须是
self - • 调用时:不需要传
self,Python 自动将调用者传入
6.4 魔法方法
魔法方法是 Python 类中以双下划线包裹的特殊方法,通过特定操作隐式触发。
魔法方法分类速查表
| 类别 | 魔法方法 | 触发时机 | 说明 |
|---|---|---|---|
| 构造与初始化 | __new__ | 创建对象时(在 __init__ 之前) | 控制对象生成(单例模式常用) |
__init__ | 对象创建后 | 初始化属性 | |
| 字符串表示 | __str__ | print() 或 str() | 返回用户友好的描述 |
__repr__ | 交互式控制台直接输入对象 | 返回开发者调试信息 | |
| 运算符重载 | __add__ | + 运算符 | 自定义加法 |
__sub__ | - 运算符 | 自定义减法 | |
__eq__ | == 运算符 | 自定义相等判断 | |
| 大小比较 | __lt__ | < | 小于 |
__le__ | <= | 小于等于 | |
__gt__ | > | 大于 | |
__ge__ | >= | 大于等于 | |
| 属性访问 | __getattr__ | 访问不存在的属性 | 提供默认值,防止报错 |
__setattr__ | 给属性赋值 | 拦截并验证赋值 | |
| 上下文管理 | __enter__ | 进入 with 块 | 打开资源 |
__exit__ | 退出 with 块 | 关闭资源 |
实例:Vector 向量类
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1) # 触发 __str__ → Vector(1, 2)
v3 = v1 + v2 # 触发 __add__
print(v3) # Vector(4, 6)
实例属性 vs 类属性
class MyClass:
class_attr = "共享数据" # 类属性:所有对象共享
def __init__(self):
self.instance_attr = 0 # 实例属性:每个对象独立
| 类型 | 归属 | 特点 |
|---|---|---|
| 实例属性 | 每个具体对象 | 各对象独立,互不影响 |
| 类属性 | 类本身 | 所有实例共享 |
第七章:异常处理(35)
7.1 try / except / finally
语法结构:
try:
# 可能出现异常的业务代码
print(my_name) # my_name 未定义,会报错
except NameError as e:
# 捕获 NameError 类型的异常
print("程序运行报错,错误信息:", e)
finally:
# 无论是否报错,都会执行
print("释放资源 ~")
执行流程:
try 代码块
├── 无异常 → 跳过 except → 执行 finally → 继续后续代码
└── 有异常 → 匹配 except → 执行 except 代码 → 执行 finally → 继续后续代码
7.2 常见异常类型
| 异常类型 | 触发场景 | 示例 |
|---|---|---|
NameError | 使用未定义的变量 | print(my_name) |
TypeError | 类型不匹配 | "hello" + 5 |
ValueError | 值不合法 | int("abc") |
IndexError | 索引越界 | lst[100] |
KeyError | 字典 key 不存在 | d["不存在的key"] |
FileNotFoundError | 文件不存在 | open("不存在.txt") |
ZeroDivisionError | 除以零 | 1 / 0 |
AttributeError | 对象没有该属性 | "str".foo() |
捕获多种异常:
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"除零错误: {e}")
except Exception as e:
print(f"未知错误: {e}")
第八章:文件操作(35-36)
8.1 文本文件读写
基本方式(手动关闭)
f = open("望庐山瀑布.txt", "r", encoding="utf-8")
content = f.read()
print(content)
f.close() # 必须手动关闭!
推荐方式(with 语句自动关闭)
with open("望庐山瀑布.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# 离开 with 块后,文件自动关闭
文件打开模式
| 模式 | 说明 |
|---|---|
"r" | 只读(默认),文件不存在则报错 |
"w" | 写入,文件不存在则创建,存在则覆盖 |
"a" | 追加,文件不存在则创建 |
"r+" | 读写 |
带异常处理的文件读取
try:
with open("望庐山瀑布.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
except FileNotFoundError:
print(f"错误:找不到文件")
except Exception as e:
print(f"发生未知错误: {e}")
8.2 JSON 序列化与反序列化
JSON 是一种通用的数据交换格式,Python 通过 json 模块实现序列化/反序列化。
写入 JSON(序列化)
import json
import os
obj = {
"name": "张三",
"age": 18,
"gender": "男",
"hobbies": ["reading", "swimming"]
}
os.makedirs("resources", exist_ok=True)
with open("resources/session.json", "w", encoding="utf-8") as f:
json.dump(obj, f, ensure_ascii=False, indent=2)
# ensure_ascii=False: 保留中文字符(不转成 \uXXXX)
# indent=2: 格式化缩进,便于阅读
读取 JSON(反序列化)
import json
with open("resources/session.json", "r", encoding="utf-8") as f:
loaded_obj = json.load(f)
print(loaded_obj) # Python 字典
print(type(loaded_obj)) # <class 'dict'>
print(loaded_obj['name']) # 张三
json 模块方法对比
| 方法 | 方向 | 说明 |
|---|---|---|
json.dump(obj, file) | Python → 文件 | 将 Python 对象序列化写入文件 |
json.load(file) | 文件 → Python | 从文件反序列化为 Python 对象 |
json.dumps(obj) | Python → 字符串 | 将 Python 对象序列化为 JSON 字符串 |
json.loads(string) | 字符串 → Python | 将 JSON 字符串反序列化为 Python 对象 |
Python 与 JSON 类型映射
| Python | JSON |
|---|---|
dict | {} 对象 |
list | [] 数组 |
str | "..." 字符串 |
int / float | 数字 |
True / False | true / false |
None | null |
8.3 CSV 文件读写
CSV(Comma-Separated Values)是最常见的表格数据格式,Python 通过内置 csv 模块操作。
写入 CSV
import csv
students = [
["姓名", "年龄", "成绩"],
["张三", 18, 92],
["李四", 19, 85],
["王五", 17, 97],
]
with open("students.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerows(students)
# writerows 一次性写入多行;也可以用 writerow 逐行写入
注意:
newline=""防止 Windows 下出现空行。
读取 CSV
import csv
with open("students.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row)
# 输出:
# ['姓名', '年龄', '成绩']
# ['张三', '18', '92']
# ['李四', '19', '85']
# ['王五', '17', '97']
使用 DictReader / DictWriter(字典方式)
以字典形式读写 CSV,自动用第一行作为字段名:
import csv
# 写入
data = [
{"name": "张三", "age": 18, "score": 92},
{"name": "李四", "age": 19, "score": 85},
]
with open("students_dict.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "score"])
writer.writeheader() # 写入表头
writer.writerows(data) # 写入数据行
# 读取
with open("students_dict.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["score"])
# 输出:
# 张三 92
# 李四 85
csv 模块方法对比
| 类 | 方法 | 说明 |
|---|---|---|
writer | writerow(row) | 写入一行(列表) |
writer | writerows(rows) | 写入多行 |
DictWriter | writeheader() | 写入表头 |
DictWriter | writerows(rows) | 写入多行(字典列表) |
reader | 迭代 | 每行返回一个列表 |
DictReader | 迭代 | 每行返回一个字典(key 为表头) |
8.4 二进制文件操作
二进制文件用于存储图片、音频、视频、序列化对象等非文本数据。打开模式使用 "rb"(读)或 "wb"(写),不需要 encoding 参数。
读写二进制文件
# 写入二进制数据
with open("data.bin", "wb") as f:
f.write(b"\x89PNG\r\n\x1a\n") # bytes 字面量
f.write(bytes([0xFF, 0xD8, 0xFF])) # 从列表构造 bytes
# 读取二进制数据
with open("data.bin", "rb") as f:
data = f.read()
print(data) # b'\x89PNG\r\n\x1a\n...'
print(type(data)) # <class 'bytes'>
print(len(data)) # 字节数
文件复制(二进制方式)
def copy_file(src: str, dst: str):
"""复制任意类型的文件(图片、视频等)"""
with open(src, "rb") as f_src:
data = f_src.read()
with open(dst, "wb") as f_dst:
f_dst.write(data)
print(f"复制完成:{src} → {dst}")
copy_file("photo.jpg", "photo_copy.jpg")
分块读取大文件
def read_large_binary(path: str, chunk_size: int = 8192):
"""分块读取,避免一次性加载大文件到内存"""
with open(path, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 处理每个 chunk
print(f"读取了 {len(chunk)} 字节")
bytes 与 str 互转
# 编码:str → bytes
text = "你好世界"
data = text.encode("utf-8")
print(data) # b'\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'
# 解码:bytes → str
recovered = data.decode("utf-8")
print(recovered) # 你好世界
常见二进制操作模式
| 模式 | 说明 | 典型用途 |
|---|---|---|
"rb" | 只读二进制 | 读取图片、音频 |
"wb" | 写入二进制(覆盖) | 保存下载的文件 |
"ab" | 追加二进制 | 拼接文件 |
"r+b" | 读写二进制 | 修改文件特定位置 |
8.5 路径操作 — pathlib 模块
pathlib 是 Python 3 推荐的路径操作库,用面向对象的方式处理文件路径,跨平台兼容。
创建 Path 对象
from pathlib import Path
# 当前目录
p = Path(".")
# 绝对路径
p = Path("/Users/demo/documents")
# 拼接路径(使用 / 运算符)
p = Path("data") / "2024" / "report.csv"
print(p) # data/2024/report.csv
# 从字符串构造
p = Path("data/2024/report.csv")
常用路径属性
p = Path("/Users/demo/documents/report.csv")
print(p.name) # report.csv — 文件名
print(p.stem) # report — 文件名(不含后缀)
print(p.suffix) # .csv — 后缀名
print(p.parent) # /Users/demo/documents — 父目录
print(p.anchor) # / — 根路径
文件与目录操作
from pathlib import Path
p = Path("data/output")
# 创建目录(递归)
p.mkdir(parents=True, exist_ok=True)
# 判断
print(p.exists()) # True / False
print(p.is_file()) # 是否是文件
print(p.is_dir()) # 是否是目录
# 列出目录下所有 .py 文件
for f in Path(".").glob("**/*.py"):
print(f)
# 读取 / 写入文本
Path("hello.txt").write_text("Hello, pathlib!", encoding="utf-8")
content = Path("hello.txt").read_text(encoding="utf-8")
print(content)
# 读取 / 写入二进制
data = Path("photo.jpg").read_bytes()
Path("photo_copy.jpg").write_bytes(data)
# 删除文件
Path("hello.txt").unlink(missing_ok=True)
# 重命名
Path("old.txt").rename("new.txt")
pathlib vs os.path 对比
| 操作 | os.path(传统) | pathlib(推荐) |
|---|---|---|
| 拼接路径 | os.path.join("a", "b") | Path("a") / "b" |
| 获取文件名 | os.path.basename(p) | Path(p).name |
| 获取后缀 | os.path.splitext(p)[1] | Path(p).suffix |
| 判断存在 | os.path.exists(p) | Path(p).exists() |
| 列出文件 | os.listdir(".") | list(Path(".").iterdir()) |
| 读取文件 | open(p).read() | Path(p).read_text() |
综合示例:批量重命名文件
from pathlib import Path
folder = Path("photos")
folder.mkdir(exist_ok=True)
# 假设目录下有 1.jpg, 2.jpg, 3.jpg ...
for i, jpg in enumerate(sorted(folder.glob("*.jpg")), start=1):
new_name = folder / f"photo_{i:03d}{jpg.suffix}"
jpg.rename(new_name)
print(f"{jpg.name} → {new_name.name}")
第九章:面向对象三大特性
9.1 封装(37)
核心思想: 把数据和操作数据的方法捆绑在一起,通过访问控制隐藏内部细节。
三种访问级别
| 级别 | 命名约定 | 说明 |
|---|---|---|
| 公有 | self.name | 外部可直接访问 |
| 受保护 | self._age | 单下划线,约定不建议外部访问 |
| 私有 | self.__health | 双下划线,外部无法直接访问(名称改写) |
示例:Animal 类
class Animal:
kingdom = "动物界" # 类属性
def __init__(self, name: str, age: int, health: int = 100):
self.name = name # 公有
self._age = age # 受保护
self.__health = health # 私有
# Getter
def get_health(self) -> int:
return self.__health
# Setter(带校验)
def set_health(self, value: int):
if value < 0:
raise ValueError("健康值不能为负数")
self.__health = value
@property 装饰器
让方法像属性一样使用,同时保留校验逻辑:
class Car:
def __init__(self, brand, model, fuel=100.0):
self.brand = brand
self.model = model
self.__fuel = fuel
self.__mileage = 0.0
@property
def fuel(self) -> float:
"""只读属性:返回当前油量"""
return self.__fuel
@property
def mileage(self) -> float:
return self.__mileage
def drive(self, distance: float):
consumption = distance * 0.1
if consumption > self.__fuel:
raise ValueError(f"油量不足!")
self.__fuel -= consumption
self.__mileage += distance
car = Car("比亚迪", "汉EV")
car.drive(200)
print(car.fuel) # 通过 @property 访问,像属性一样
print(car.mileage) # 只读属性
9.2 继承(38)
核心思想: 子类自动拥有父类的属性和方法,实现代码复用;子类可以扩展或覆盖(override)父类行为。
基本语法
class Animal:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def speak(self) -> str:
return f"{self.name} 发出了声音"
def eat(self, food: str):
print(f"{self.name} 正在吃 {food}")
class Dog(Animal):
def __init__(self, name: str, age: int, breed: str):
super().__init__(name, age) # 调用父类 __init__
self.breed = breed # 子类新增属性
def speak(self) -> str: # 重写(Override)父类方法
return f"{self.name} 汪汪叫!"
def fetch(self, item: str): # 子类新增方法
print(f"{self.name} 去捡 {item} 了")
关键概念
| 概念 | 说明 |
|---|---|
super().__init__() | 调用父类的构造方法,必须写在子类 __init__ 中 |
| 方法重写(Override) | 子类定义与父类同名的方法,覆盖父类行为 |
isinstance(obj, cls) | 判断对象是否是某类(或其子类)的实例 |
issubclass(A, B) | 判断 A 是否是 B 的子类 |
示例:Vehicle 继承体系
class Vehicle:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
def move(self):
print(f"{self.brand} 正在行驶")
class Car(Vehicle):
def __init__(self, brand, speed, seats):
super().__init__(brand, speed)
self.seats = seats
def move(self): # 重写
print(f"{self.brand} 轿车({self.seats}座)正在行驶")
class Truck(Vehicle):
def __init__(self, brand, speed, payload):
super().__init__(brand, speed)
self.payload = payload
def move(self): # 重写
print(f"{self.brand} 卡车(载重{self.payload}t)正在行驶")
9.3 多继承与 MRO(39)
核心思想: 一个子类同时继承多个父类;Python 用 C3 线性化算法(MRO) 确定方法查找顺序。
Mixin 模式 — 功能组合
class Flyable:
def fly(self):
print(f"{self.__class__.__name__} 正在飞翔")
class Swimmable:
def swim(self):
print(f"{self.__class__.__name__} 正在游泳")
class Animal:
def __init__(self, name):
self.name = name
class Duck(Flyable, Swimmable, Animal):
def __init__(self, name):
Animal.__init__(self, name)
def speak(self):
return f"{self.name} 嘎嘎嘎!"
duck = Duck("唐老鸭")
duck.fly() # 来自 Flyable
duck.swim() # 来自 Swimmable
MRO 方法解析顺序
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Flyable'>, <class 'Swimmable'>, <class 'Animal'>, <class 'object'>)
同名方法冲突: 当多个父类有同名方法时,按 MRO 顺序,排在前面的优先。
菱形继承与 super() 协作
class A:
def __init__(self):
super().__init__()
class B(A):
def __init__(self):
super().__init__()
class C(A):
def __init__(self):
super().__init__()
class D(B, C):
"""MRO: D -> B -> C -> A -> object"""
def __init__(self):
super().__init__() # 沿 MRO 链调用,每个 __init__ 只执行一次
9.4 多态(40)
核心思想: 同一个接口,不同的类有不同的实现;调用方无需关心对象的具体类型。
方式一:抽象基类(ABC)
from abc import ABC, abstractmethod
class Animal(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def speak(self) -> str:
pass # 抽象方法,子类必须实现
class Dog(Animal):
def speak(self): return f"{self.name} 汪汪!"
class Cat(Animal):
def speak(self): return f"{self.name} 喵喵~"
# 多态的核心用法
def make_all_speak(animals: list[Animal]):
for animal in animals:
print(animal.speak()) # 行为由对象的真实类型决定
make_all_speak([Dog("旺财"), Cat("咪咪")])
抽象基类的特点:
- • 使用
ABC和@abstractmethod定义 - • 抽象类不能直接实例化,会报
TypeError - • 子类必须实现所有抽象方法,否则也不能实例化
方式二:鸭子类型(Duck Typing)
class Robot:
"""不继承 Animal,但也有 speak 方法"""
def speak(self):
return f"机器人:您好"
def make_speak(obj):
"""只要对象有 speak() 方法就能调用,不检查类型"""
print(obj.speak())
make_speak(Dog("小黑")) # OK
make_speak(Robot("GPT-9000")) # 也 OK!
鸭子类型哲学: "如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。"
第十章:综合项目 — 图书管理系统(41)
这是一个综合运用封装、继承、多态、异常处理的完整项目。
10.1 项目结构
图书管理系统/
├── main.py # 主程序入口,初始化数据并运行场景
├── book.py # Book 类:图书实体
├── member.py # Member 类体系:会员(普通/VIP)
└── library.py # Library 类:图书馆管理核心
设计模式:
- • Book — 数据实体(封装库存管理)
- • Member — 抽象基类 + 继承(RegularMember / VipMember)
- • Library — 业务控制器(协调 Book 和 Member 的交互)
10.2 Book 类
class Book:
def __init__(self, book_id: str, title: str, author: str, stock: int = 1):
self.book_id = book_id
self.title = title
self.author = author
self.__stock = stock # 私有属性:库存
@property
def stock(self) -> int: # 只读属性
return self.__stock
def borrow(self):
if self.__stock <= 0:
raise ValueError(f"《{self.title}》库存不足")
self.__stock -= 1
def return_book(self):
self.__stock += 1
def __repr__(self) -> str:
return f"[{self.book_id}] 《{self.title}》 / {self.author}(库存:{self.__stock})"
设计要点:
- •
__stock是私有属性,外部无法直接修改 - •
stock是只读的@property,只能看不能改 - • 借书/还书通过方法控制,内部做库存校验
10.3 Member 类体系
继承关系:
Member(基类)
├── RegularMember(普通会员,最多借 3 本)
└── VipMember(VIP 会员,最多借 6 + vip_level 本)
class Member:
def __init__(self, card_id, password, name):
self.card_id = card_id
self.__password = password # 私有属性:密码
self.name = name
self._borrowed: list[Book] = [] # 受保护:借阅列表
def check_password(self, password) -> bool:
return self.__password == password
def max_borrow(self) -> int:
raise NotImplementedError("子类必须实现")
def borrow_book(self, book: Book):
if len(self._borrowed) >= self.max_borrow():
print(f"借书失败:已达最大借书数量({self.max_borrow()} 本)")
return
book.borrow()
self._borrowed.append(book)
def return_book(self, book_id: str):
for book in self._borrowed:
if book.book_id == book_id:
self._borrowed.remove(book)
book.return_book()
return
class RegularMember(Member):
def max_borrow(self) -> int:
return 3
class VipMember(Member):
def __init__(self, card_id, password, name, vip_level=1):
super().__init__(card_id, password, name)
self.vip_level = vip_level
def max_borrow(self) -> int:
return 6 + self.vip_level
多态体现: borrow_book() 中调用 self.max_borrow(),具体行为由子类决定。
10.4 Library 类
class Library:
def __init__(self, name: str):
self.name = name
self.__books: dict[str, Book] = {}
self.__members: dict[str, Member] = {}
self.__current: Optional[Member] = None # 当前登录用户
def login(self, card_id, password) -> bool:
member = self.__members.get(card_id)
if member is None:
print("登录失败:卡号不存在")
return False
if not member.check_password(password):
print("登录失败:密码错误")
return False
self.__current = member
return True
def borrow(self, book_id: str):
member = self._require_login()
if not member:
return
book = self.__books.get(book_id)
try:
member.borrow_book(book)
except ValueError as e:
print(f"借书失败:{e}")
设计要点:
- •
__current追踪当前登录用户,所有操作需要先登录 - •
_require_login()是内部辅助方法,检查登录状态 - • 使用
try/except处理库存不足等业务异常
10.5 完整可运行代码
以下是整个项目的完整代码,包含四个文件。
book.py — 图书实体
class Book:
def __init__(self, book_id: str, title: str, author: str, stock: int = 1):
self.book_id = book_id
self.title = title
self.author = author
self.__stock = stock
@property
def stock(self) -> int:
return self.__stock
def borrow(self):
if self.__stock <= 0:
raise ValueError(f"《{self.title}》库存不足")
self.__stock -= 1
def return_book(self):
self.__stock += 1
def __repr__(self) -> str:
return f"[{self.book_id}] 《{self.title}》 / {self.author}(库存:{self.__stock})"
member.py — 会员类体系
from __future__ import annotations
from typing import Optional
from book import Book
class Member:
def __init__(self, card_id: str, password: str, name: str):
self.card_id = card_id
self.__password = password
self.name = name
self._borrowed: list[Book] = []
def check_password(self, password: str) -> bool:
return self.__password == password
def max_borrow(self) -> int:
raise NotImplementedError("子类必须实现 max_borrow()")
def borrow_limit_desc(self) -> str:
raise NotImplementedError("子类必须实现 borrow_limit_desc()")
def borrow_book(self, book: Book):
if len(self._borrowed) >= self.max_borrow():
print(f"借书失败:{self.name} 已达最大借书数量({self.max_borrow()} 本)")
return
book.borrow()
self._borrowed.append(book)
print(f"借书成功:{self.name} 借阅了 《{book.title}》")
def return_book(self, book_id: str):
for book in self._borrowed:
if book.book_id == book_id:
self._borrowed.remove(book)
book.return_book()
print(f"还书成功:{self.name} 归还了 《{book.title}》")
return
print(f"还书失败:借阅列表中没有 ID 为 {book_id!r} 的书")
def show_borrowed(self):
if not self._borrowed:
print(f"{self.name} 当前没有借阅任何书籍")
return
print(f"{self.name} 当前借阅({len(self._borrowed)} 本):")
for book in self._borrowed:
print(f" - {book}")
def __repr__(self) -> str:
return f"{self.__class__.__name__}(card={self.card_id!r}, name={self.name!r})"
class RegularMember(Member):
def max_borrow(self) -> int:
return 3
def borrow_limit_desc(self) -> str:
return "普通会员,最多可借 3 本"
class VipMember(Member):
def __init__(self, card_id: str, password: str, name: str, vip_level: int = 1):
super().__init__(card_id, password, name)
self.vip_level = vip_level
def max_borrow(self) -> int:
return 6 + self.vip_level
def borrow_limit_desc(self) -> str:
return f"VIP{self.vip_level} 会员,最多可借 {self.max_borrow()} 本"
library.py — 图书馆管理核心
from __future__ import annotations
from typing import Optional
from book import Book
from member import Member
class Library:
def __init__(self, name: str):
self.name = name
self.__books: dict[str, Book] = {}
self.__members: dict[str, Member] = {}
self.__current: Optional[Member] = None
def add_book(self, book: Book):
self.__books[book.book_id] = book
def register_member(self, member: Member):
self.__members[member.card_id] = member
def login(self, card_id: str, password: str) -> bool:
member = self.__members.get(card_id)
if member is None:
print("登录失败:卡号不存在")
return False
if not member.check_password(password):
print("登录失败:密码错误")
return False
self.__current = member
print(f"登录成功!欢迎,{member.name}({member.borrow_limit_desc()})")
return True
def logout(self):
if self.__current:
print(f"再见,{self.__current.name}!")
self.__current = None
def _require_login(self) -> Optional[Member]:
if self.__current is None:
print("请先登录")
return None
return self.__current
def borrow(self, book_id: str):
member = self._require_login()
if not member:
return
book = self.__books.get(book_id)
if not book:
print(f"借书失败:图书馆没有 ID 为 {book_id!r} 的书")
return
try:
member.borrow_book(book)
except ValueError as e:
print(f"借书失败:{e}")
def return_book(self, book_id: str):
member = self._require_login()
if member:
member.return_book(book_id)
def show_my_books(self):
member = self._require_login()
if member:
member.show_borrowed()
def show_all_books(self):
print(f"\n【{self.name}馆藏书目】")
for book in self.__books.values():
print(f" {book}")
main.py — 主程序入口与完整运行场景
from book import Book
from member import RegularMember, VipMember
from library import Library
def init_library() -> Library:
"""初始化图书馆,添加书籍和会员"""
lib = Library("社区图书馆")
# 添加图书
books = [
Book("AI001", "LangChain入门与实践", "李智慧", stock=8),
Book("AI002", "深度学习基础教程", "王小明", stock=5),
Book("AI003", "Python数据科学手册", "Jake VanderPlas", stock=3),
Book("CS001", "算法导论(第4版)", "Thomas Cormen", stock=2),
Book("CS002", "操作系统概念", "Abraham Silberschatz", stock=4),
Book("LT001", "红楼梦", "曹雪芹", stock=6),
Book("LT002", "三体", "刘慈欣", stock=10),
]
for book in books:
lib.add_book(book)
# 注册会员
lib.register_member(RegularMember("C001", "123456", "张三"))
lib.register_member(RegularMember("C002", "654321", "王五"))
lib.register_member(VipMember("V001", "abcdef", "李四", vip_level=2))
lib.register_member(VipMember("V002", "vip888", "赵六", vip_level=1))
return lib
def print_separator(title: str):
print(f"\n{'='*50}")
print(f" {title}")
print(f"{'='*50}")
if __name__ == "__main__":
lib = init_library()
# ===== 场景一:查看馆藏 =====
print_separator("场景一:查看馆藏")
lib.show_all_books()
# ===== 场景二:未登录尝试借书 =====
print_separator("场景二:未登录尝试借书")
lib.borrow("AI001") # → 请先登录
lib.show_my_books() # → 请先登录
# ===== 场景三:密码错误 =====
print_separator("场景三:登录失败")
lib.login("C001", "wrong") # → 密码错误
lib.login("C999", "123456") # → 卡号不存在
# ===== 场景四:普通会员借书(上限 3 本)=====
print_separator("场景四:普通会员张三借书")
lib.login("C001", "123456")
lib.borrow("AI001") # 借书成功
lib.borrow("AI002") # 借书成功
lib.borrow("CS001") # 借书成功(已达上限 3 本)
lib.show_my_books()
lib.borrow("AI003") # → 已达最大借书数量
# ===== 场景五:还书后再借 =====
print_separator("场景五:还书后再借")
lib.return_book("AI002") # 归还《深度学习基础教程》
lib.show_my_books()
lib.borrow("AI003") # 还了一本,可以再借
# ===== 场景六:还一本不存在的书 =====
print_separator("场景六:异常还书")
lib.return_book("NOTEXIST") # → 借阅列表中没有
# ===== 场景七:VIP2 会员借书(上限 8 本)=====
print_separator("场景七:VIP2 会员李四借书")
lib.login("V001", "abcdef")
lib.borrow("AI001")
lib.borrow("AI002")
lib.borrow("AI003")
lib.borrow("CS001")
lib.borrow("CS002")
lib.borrow("LT001")
lib.borrow("LT002")
lib.show_my_books() # 已借 7 本
lib.borrow("AI001") # 第 8 本 → 成功(上限 8 本)
lib.show_my_books()
lib.borrow("CS002") # 第 9 本 → 超出上限
# ===== 场景八:库存耗尽 =====
print_separator("场景八:库存耗尽测试")
lib.login("C002", "654321")
lib.borrow("CS001") # CS001 库存 2,已被借走 2 本
# → 《算法导论》库存不足
# ===== 场景九:注销 =====
print_separator("场景九:注销")
lib.logout()
lib.borrow("AI001") # → 请先登录
# ===== 最终状态 =====
print_separator("最终馆藏状态")
lib.show_all_books()
运行效果示例
==================================================
场景一:查看馆藏
==================================================
【社区图书馆馆藏书目】
[AI001] 《LangChain入门与实践》 / 李智慧(库存:8)
[AI002] 《深度学习基础教程》 / 王小明(库存:5)
[AI003] 《Python数据科学手册》 / Jake VanderPlas(库存:3)
[CS001] 《算法导论(第4版)》 / Thomas Cormen(库存:2)
[CS002] 《操作系统概念》 / Abraham Silberschatz(库存:4)
[LT001] 《红楼梦》 / 曹雪芹(库存:6)
[LT002] 《三体》 / 刘慈欣(库存:10)
==================================================
场景二:未登录尝试借书
==================================================
请先登录
请先登录
==================================================
场景四:普通会员张三借书
==================================================
登录成功!欢迎,张三(普通会员,最多可借 3 本)
借书成功:张三 借阅了 《LangChain入门与实践》
借书成功:张三 借阅了 《深度学习基础教程》
借书成功:张三 借阅了 《算法导论(第4版)》
张三 当前借阅(3 本):
- [AI001] 《LangChain入门与实践》 / 李智慧(库存:7)
- [AI002] 《深度学习基础教程》 / 王小明(库存:4)
- [CS001] 《算法导论(第4版)》 / Thomas Cormen(库存:1)
借书失败:张三 已达最大借书数量(3 本)
项目知识点总结
| 章节 | 在项目中的体现 |
|---|---|
| 封装 | Book.__stock 私有属性、Member.__password 私有属性、@property 只读库存 |
| 继承 | RegularMember / VipMember 继承 Member,super().__init__() |
| 多态 | max_borrow() 和 borrow_limit_desc() 由子类实现,borrow_book() 统一调用 |
| 异常处理 | Book.borrow() 抛出 ValueError,Library.borrow() 中 try/except 捕获 |
| 魔法方法 | __repr__ 自定义对象打印格式 |
| 模块导入 | 三个文件互相 import,from __future__ import annotations 延迟类型注解 |
附录:面向对象核心概念速查
三大特性对比
| 特性 | 核心思想 | 关键词 | 示例 |
|---|---|---|---|
| 封装 | 隐藏内部细节,暴露安全接口 | 私有属性、@property、getter/setter | car.__fuel 不可直接访问 |
| 继承 | 子类复用父类代码,可扩展/覆盖 | super()、重写、isinstance | Dog(Animal) |
| 多态 | 同一接口,不同实现 | 抽象基类、鸭子类型 | animal.speak() 行为因类型而异 |
类的特殊属性/方法
| 名称 | 说明 |
|---|---|
__dict__ | 对象的所有属性(字典形式) |
__class__ | 对象所属的类 |
__mro__ | 方法解析顺序(多继承查找顺序) |
__name__ | 类/模块的名称 |
__doc__ | 文档字符串 |
设计原则总结
| 原则 | 说明 |
|---|---|
| 单一职责 | 一个类只做一件事(Book 管书、Member 管人、Library 管协调) |
| 开闭原则 | 对扩展开放,对修改关闭(新增 VIP 等级只需新建子类) |
| 依赖倒置 | 依赖抽象,不依赖具体(Library 依赖 Member 基类,不依赖具体子类) |