PYTHON LESSON 064
综合项目:班级图书角借阅系统
综合运用类、字符串查找、队列与函数,完成一个能搜书、借书、预约、还书的完整系统。
00 · 学习目标
这一课要解决什么?
学完后,你应该能够
- class 定义 Book 和 Library 两张图纸
- 关键字 in 标题(忽略大小写)做检索
- queue 列表 + pop(0) 实现预约排队
01 · 核心概念
类 + 字符串检索 + 队列:把本单元全部本领串进一个真实系统
类 + 字符串检索 + 队列:把本单元全部本领串进一个真实系统:系统里有两种“东西”:书和图书角。Book 管一本书的状态(借出没、谁在排队),Library 管所有书(上架、检索、借还)。把大问题拆成两个类,每个类的职责一句话能说清,设计就合格。
GESP Python 4 级 · 类的创建GESP Python 4 级 · 队列与循环队列GESP Python 4 级 · 字符串及其函数课程使用普通类解释对象模型;dataclass、property、抽象基类和元类是后续工程工具。
02 · 语法与规则
先记住这 3 条,再开始写程序
class 定义 Book 和 Library 两张图纸先准确读出这条写法的结构与作用。
关键字 in 标题(忽略大小写)做检索换一组最小数据,手工推演一次结果。
queue 列表 + pop(0) 实现预约排队再用边界值或反例确认它的适用条件。
03 · 完整实例
代码、运行结果和解释放在一起看
# 综合项目:班级图书角借阅系统
class Book:
"""一本书:书名、是否借出、预约队列"""
def __init__(self, title):
self.title = title
self.borrowed = False
self.queue = [] # 预约队列:先来先得
class Library:
"""图书角:管理所有的书"""
def __init__(self):
self.books = []
def add_book(self, title):
self.books.append(Book(title))
def find(self, keyword):
"""按关键字找书(忽略大小写)"""
result = []
for b in self.books:
if keyword.lower() in b.title.lower():
result.append(b)
return result
def borrow(self, title, student):
for b in self.books:
if b.title == title:
if not b.borrowed:
b.borrowed = True
print(student + " 借到了《" + title + "》")
else:
b.queue.append(student)
print("《" + title + "》已借出," + student + " 排队第 " + str(len(b.queue)) + " 位")
return
print("没有找到《" + title + "》")
def give_back(self, title):
for b in self.books:
if b.title == title:
if b.queue:
nxt = b.queue.pop(0) # 队首的同学优先
print("《" + title + "》归还,直接传给排队的 " + nxt)
else:
b.borrowed = False
print("《" + title + "》已归还上架")
return
lib = Library()
lib.add_book("Python 编程入门")
lib.add_book("哈利波特")
lib.add_book("Python 进阶魔法")
found = lib.find("python")
print("搜 python 找到", len(found), "本:")
for b in found:
print(" -", b.title)
lib.borrow("哈利波特", "小明")
lib.borrow("哈利波特", "小红")
lib.borrow("哈利波特", "小刚")
lib.give_back("哈利波特")搜 python 找到 2 本: - Python 编程入门 - Python 进阶魔法 小明 借到了《哈利波特》 《哈利波特》已借出,小红 排队第 1 位 《哈利波特》已借出,小刚 排队第 2 位 《哈利波特》归还,直接传给排队的 小红
书被借出时预约者进队列(append),归还时队首优先(pop(0))——队列保证了公平。
04 · 逐步理解
每一步只解决一个问题
- 01
先画两张图纸
系统里有两种“东西”:书和图书角。Book 管一本书的状态(借出没、谁在排队),Library 管所有书(上架、检索、借还)。把大问题拆成两个类,每个类的职责一句话能说清,设计就合格。
class Book: def __init__(self, title): self.title = title self.borrowed = False self.queue = [] - 02
检索:字符串的 in 加上忽略大小写
find 方法遍历书架,keyword.lower() in b.title.lower() 两边都转小写再判断包含——搜 python 也能找到 Python 开头的书。这就是字符串考点在真实项目里的样子。
if keyword.lower() in b.title.lower(): result.append(b) - 03
借书:两种结局
找到书后分岔:没借出,就标记 borrowed = True 借给同学;已借出,就把同学 append 进预约队列,告诉他排第几位。排第几位就是 len(queue),队列的长度直接变成号码牌。
if not b.borrowed: b.borrowed = True else: b.queue.append(student) - 04
还书:队列决定公平
归还时看预约队列:有人排队就 pop(0) 请队首的同学,书直接传给他;没人排队才 borrowed = False 重新上架。先进先出,谁先预约谁先得——队列把“公平”翻译成了代码。
if b.queue: nxt = b.queue.pop(0) else: b.borrowed = False - 05
为什么 borrow 和 give_back 写在 Library 里
借还操作要翻遍书架找书,属于“管理所有书”的职责,所以写在 Library 里;而“饿不饿”是宠物自己的事,所以写在 Pet 里。判断方法归属的口诀:这个操作需要谁的数据,就写在谁家里。
- 06
升级挑战:热门图书榜
给 Book 加一个 borrow_count 属性,每次成功借出加 1。再写一个 hot_list 方法,按借阅次数从多到少打印排行榜。做完这个,你就把本单元的类、字符串、队列全部串了一遍——4 级稳了!
self.borrow_count = 0
05 · 练习与检验
自己写出来,才算真正学会
- 1运行系统,观察借书、排队、还书全流程
- 2给图书角再添两本你爱看的书
- 3测试还书时无人排队的情况
- 4挑战:给 Book 加借阅次数统计并排出热门榜
06 · 完整知识
继续理解定义、规则和适用边界
第一次学习先完成上面的六个步骤;需要查定义、核对规则、分析误区或理解“为什么”时,再展开对应知识章。
程序组织类、对象、属性与方法理解对象模型和封装边界,用类表达有状态的实体,而不是把所有程序都强行改成类。+
正式定义
类描述一类对象的数据与行为;实例是类创建的具体对象。实例方法的第一个参数通常命名为 self,用来访问当前实例;__init__ 在实例创建后负责初始化状态。
必须掌握
- 实例属性通常在 __init__ 中通过 self.name = value 建立。
- 实例方法通过 object.method() 调用;Python 会自动把实例绑定给 self。
- 类属性由实例共享,实例属性属于单个对象;同名实例属性会遮蔽类属性。
- __repr__ 面向开发与调试,__str__ 面向用户显示;特殊方法应遵守其协议。
- 继承表达“是一种”关系,组合表达“拥有一个”关系;能用组合清晰表达时不要滥用继承。
- 对象相等默认仍是身份比较;需要按内容相等时要定义相应协议。
常见误区
- 在方法中漏写 self
- 把每个实例独有的可变数据写成类属性
- 只为包装几个无状态函数而建类
- 继承层次过深导致行为难以追踪
适用边界
- 课程使用普通类解释对象模型;dataclass、property、抽象基类和元类是后续工程工具。
- 算法题常用函数和基本容器更直接,不要求为了“面向对象”而增加结构。
编码与文本字符串、转义、切片与格式化从不可变字符序列到检索、拆分、拼接、格式化和常用判断方法。+
正式定义
str 是不可变的 Unicode 字符序列。下标访问单个字符,切片生成新字符串;任何看似“修改字符串”的方法都会返回新对象。
必须掌握
- 下标从 0 开始,负下标从末尾开始;切片 s[start:stop:step] 不包含 stop,step 不能为 0。
- 转义序列用于在字面量中表示换行、制表、引号、反斜杠或码点;原始字符串仍有末尾反斜杠限制。
- find 找不到返回 -1,index 找不到抛 ValueError;count 统计不重叠出现次数。
- split 把字符串拆成列表,join 用一个字符串连接可迭代对象中的字符串,strip 只删除两端字符。
- f-string 的格式说明可控制宽度、对齐、精度、进制和百分比;格式化不改变原值。
- isalpha/isdigit 等按 Unicode 定义,不只识别英文字母和 ASCII 数字。
常见误区
- 尝试 s[0] = 'A' 原地修改字符串
- 把 strip('ab') 误解为删除完整子串 'ab'
- find 返回 -1 后直接拿去当有效下标
- 把字节长度与字符长度混为一谈
适用边界
- 正则表达式不在低等级字符串必修范围,但复杂模式匹配时应使用 re,而不是堆叠大量 split/find。
- 面向用户的字符计数还可能涉及组合字符和字素簇,len 统计的是 Unicode 码点序列长度。
算法方法算法、复杂度与解题验证把题意转成输入、状态、规则与输出,用正确性和复杂度共同评价解法。+
正式定义
算法是解决一类问题的有限、明确步骤。正确性说明算法对所有满足前置条件的输入都得到规定结果;时间和空间复杂度描述输入规模增长时资源使用的增长量级。
必须掌握
- 先明确输入规模 n、数据范围、目标和允许误差,再选择数据结构与算法。
- O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ) 表示增长量级,不是精确运行秒数。
- 顺序代码复杂度取较大项,嵌套循环常相乘,二分每步把范围缩小一半。
- 正确性可用循环不变量、数学归纳、交换论证、反证或状态定义来说明。
- 样例只验证少量输入;必须自己设计边界、极端、重复、有序/逆序和无解数据。
- 优化前先得到正确基线并测量瓶颈,不为小数据盲目增加复杂实现。
常见误区
- 只看样例通过就宣称正确
- 不看数据范围使用 O(n²)
- 二分区间开闭混用
- 把 O(n) 当成永远比 O(log n) 慢固定倍数
适用边界
- 复杂度隐藏常数与硬件差异,但仍是比较规模增长的核心工具。
- 考场策略、课程完成度和算法能力是不同证据,任何单项都不能保证考级通过。
核心数据结构栈、队列、链表与并查集按访问顺序和更新需求理解线性结构,并掌握各操作的真实代价。+
正式定义
栈按后进先出访问,队列按先进先出访问,链表用节点引用连接次序,并查集维护元素所属的动态不相交集合。数据结构的价值在于为特定操作提供清晰语义和复杂度保证。
必须掌握
- Python list 的尾部 append/pop 可作栈,均摊 O(1)。
- 队列应使用 collections.deque 的 append/popleft,避免 list.pop(0) 的 O(n) 搬移。
- 循环队列用固定数组、队首队尾下标和取模复用空间,必须约定空与满的判定。
- 单链表节点保存值和 next;已知前驱时插入删除 O(1),按下标查找仍是 O(n)。
- 并查集的 find 找代表元,union 合并集合;路径压缩与按大小/秩合并使均摊代价近似常数。
- 选择结构前先列出最频繁操作:随机访问、两端操作、按键查找或集合合并。
常见误区
- 用 pop(0) 实现大规模队列
- 空栈空队列仍然弹出
- 链表改指针时丢失后续节点
- 并查集只改父节点却不理解代表元
适用边界
- Python 没有课程必需的内置链表类型,教学实现用于理解指针关系;工程中应根据实际操作选择成熟容器。
- 并查集擅长连通性合并,不支持高效删除或一般最短路。
完成检查