项目建议 90 分钟学习等级 4/8

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 条,再开始写程序

01class 定义 Book 和 Library 两张图纸

先准确读出这条写法的结构与作用。

02关键字 in 标题(忽略大小写)做检索

换一组最小数据,手工推演一次结果。

03queue 列表 + pop(0) 实现预约排队

再用边界值或反例确认它的适用条件。

03 · 完整实例

代码、运行结果和解释放在一起看

g4-library-project.pyPYTHON 3.12
# 综合项目:班级图书角借阅系统
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("哈利波特")
运行结果OUTPUT
搜 python 找到 2 本:
 - Python 编程入门
 - Python 进阶魔法
小明 借到了《哈利波特》
《哈利波特》已借出,小红 排队第 1 位
《哈利波特》已借出,小刚 排队第 2 位
《哈利波特》归还,直接传给排队的 小红

书被借出时预约者进队列(append),归还时队首优先(pop(0))——队列保证了公平。

在新标签运行和修改这个实例已装入本课代码 · 可自定义输入 · 可提交判题

04 · 逐步理解

每一步只解决一个问题

  1. 01

    先画两张图纸

    系统里有两种“东西”:书和图书角。Book 管一本书的状态(借出没、谁在排队),Library 管所有书(上架、检索、借还)。把大问题拆成两个类,每个类的职责一句话能说清,设计就合格。

    PYTHON
    class Book:
        def __init__(self, title):
            self.title = title
            self.borrowed = False
            self.queue = []
  2. 02

    检索:字符串的 in 加上忽略大小写

    find 方法遍历书架,keyword.lower() in b.title.lower() 两边都转小写再判断包含——搜 python 也能找到 Python 开头的书。这就是字符串考点在真实项目里的样子。

    PYTHON
    if keyword.lower() in b.title.lower():
        result.append(b)
  3. 03

    借书:两种结局

    找到书后分岔:没借出,就标记 borrowed = True 借给同学;已借出,就把同学 append 进预约队列,告诉他排第几位。排第几位就是 len(queue),队列的长度直接变成号码牌。

    PYTHON
    if not b.borrowed:
        b.borrowed = True
    else:
        b.queue.append(student)
  4. 04

    还书:队列决定公平

    归还时看预约队列:有人排队就 pop(0) 请队首的同学,书直接传给他;没人排队才 borrowed = False 重新上架。先进先出,谁先预约谁先得——队列把“公平”翻译成了代码。

    PYTHON
    if b.queue:
        nxt = b.queue.pop(0)
    else:
        b.borrowed = False
  5. 05

    为什么 borrow 和 give_back 写在 Library 里

    借还操作要翻遍书架找书,属于“管理所有书”的职责,所以写在 Library 里;而“饿不饿”是宠物自己的事,所以写在 Pet 里。判断方法归属的口诀:这个操作需要谁的数据,就写在谁家里。

  6. 06

    升级挑战:热门图书榜

    给 Book 加一个 borrow_count 属性,每次成功借出加 1。再写一个 hot_list 方法,按借阅次数从多到少打印排行榜。做完这个,你就把本单元的类、字符串、队列全部串了一遍——4 级稳了!

    PYTHON
    self.borrow_count = 0

05 · 练习与检验

自己写出来,才算真正学会

本课实作步骤
  1. 1运行系统,观察借书、排队、还书全流程
  2. 2给图书角再添两本你爱看的书
  3. 3测试还书时无人排队的情况
  4. 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 没有课程必需的内置链表类型,教学实现用于理解指针关系;工程中应根据实际操作选择成熟容器。
  • 并查集擅长连通性合并,不支持高效删除或一般最短路。
打开本章完整示例与独立阅读页 →

完成检查

确认自己会解释、会编写、会验证