带你用一个 Python 图书系统,通关封装继承多态

很多人学面向对象(OOP),看了好几遍猫猫狗狗、汽车鸣笛的例子,轮到自己写项目时还是无从下手。

今天我们拒绝源码赏析,直接开启"手把手带敲"模式。电脑前准备好你的代码编辑器,新建一个名为 library_system.py 的空文件。跟着文章每敲完一部分,我们就会用命令行运行测试一次。

等打通所有关卡,你会发现这个精密的系统已经自发在你手里诞生了!


🧭 准备关:搭建文件的地基

任何大楼都要先打地基。请在你的空 library_system.py 文件最顶部,敲下这几行代码。这包含了我们后面需要用到的工具库,以及一个控制借书权限的全局变量。

python 复制代码
from abc import ABC, abstractmethod
import json

# 定义一个全局变量,表示会员的借阅上限
BASE_BORROW_LIMIT = 3

📚 这里的技术点科普:

  • from abc import ...abc 是 Abstract Base Classes(抽象基类)的缩写。它是 Python 用来定规矩、立契约的工具。我们用它来确保后面的会员系统不管怎么变,都必须遵守相同的行业标准。
  • 全局变量:我们把 BASE_BORROW_LIMIT = 3 放在最顶层,意思是这是全系统公用的初始标准。以后就算图书馆想把上限改成 5 本,我们也只需要改这一行数字,不用去代码堆里到处找,这叫便于维护。

🧭 第一关:构建图书类 ------ 搞懂【封装】

有了地基,我们来写第一个类 Book。我们要把书的信息锁在类内部,不让外面的人随随便便把库存数量改错(比如不小心改出负数)。

1.1 敲入图书类代码

请紧接着在刚才的基础变量下方,敲入 Book 类的实现:

python 复制代码
class Book:
    def __init__(self, book_id, title, author, total_quantity, available_quantity):
        # 初始化书籍信息
        self.book_id = book_id
        self.title = title
        self.author = author
        self.total_quantity = total_quantity
        # 🔒 封装的核心:定义一个私有变量,表示书籍的余量
        self.__available_quantity = available_quantity

    # 定义一个获取书籍余量的方法
    def get_available_quantity(self):
        return self.__available_quantity

    # 定义一个设置书籍余量的方法
    def set_available_quantity(self, available_quantity):
        self.__available_quantity = available_quantity

    # 格式化输出书籍信息
    def __str__(self):
        return f"编号:{self.book_id},书名:{self.title},作者:{self.author},总数量:{self.total_quantity},余量:{self.get_available_quantity()}"

🔍 代码大白话拆解与科普:

  • __init__ 构造函数:每次我们要创建一本真实的书(专业术语叫实例化)时,Python 会自动调用这个方法,把书名、作者等数据塞进这个对象的口袋里。
  • 私有变量 self.__available_quantity:注意看变量名前面的双下划线 __!这是 Python 的安全锁。一旦加了它,外面的代码就无法直接通过 book.__available_quantity = -5 这种恶作剧的方式去改库存了。
  • Getter/Setter 机制(get_... / set_...):既然库存被锁死了,外面怎么看库存、改库存呢?我们专门开了两个公开的"安全通道"方法。外部必须客客气气地调用 set_available_quantity() 才能改库存。实际开发中,我们可以在 set 方法内部加一句校验:if available_quantity < 0: 拦截报错,数据安全感直接拉满!
  • 魔术方法 __str__:这是 Python 内置的特殊方法。它的作用是:当别人在外面直接执行 print(book) 时,Python 不会打印出一串看不懂的内存地址,而是会自动触发这个方法,打印出你精心拼装的漂亮字符串。

⚙️ 见证成就感时刻(第一次命令行测试)

现在,我们在刚写好的 Book 类最下方,临时敲入以下测试代码:

css 复制代码
if __name__ == "__main__":
    book = Book("B001", "Python编程:从入门到实践", "Eric Matthes", 10, 10)
    print(book)
    
    book.set_available_quantity(9)
    print(f"安全通道修改后的余量:{book.get_available_quantity()}")

打开你的终端(Windows 用 cmdPowerShell,Mac/Linux 用 Terminal),切换到你保存文件的目录下,敲入以下命令并回车:

复制代码
python library_system.py

运行结果:

编号:B001,书名:Python编程:从入门到实践...余量:10

安全通道修改后的余量:9

这就是面向对象的第一个魔法:【封装】。把数据锁在家里,通过公开的方法去访问。

⚠️ 注意:测试成功后,请把刚才临时加的最底下 5 行测试块删掉,我们要继续往上盖楼了!


🧭 第二关:构建会员体系 ------ 搞懂【继承与抽象】

接下来做会员。我们系统里有两种人:普通会员最多借 3 本;VIP 会员由于身份尊贵,借书上限会随着等级增加。

为了避免重复写两遍借还书的代码,我们先写一个统一的"老爸类"定下规范契约。

2.1 敲入会员抽象类

请在 Book 类的正下方,紧接着敲入父类 Member 的代码:

python 复制代码
class Member(ABC):
    def __init__(self, card_no, password, name):
        # 初始化成员信息
        self.card_no = card_no
        self.password = password
        self.name = name
        # 初始化借阅列表
        self.borrowed_books = []

    @abstractmethod
    # 定义一个抽象方法,子类必须实现
    def get_max_book(self):
        pass

    def borrow_book(self, book: Book):
        # 如果借阅的书籍数量小于设定值,则将书籍添加到借阅列表中并返回True,否则返回False
        if len(self.borrowed_books) < self.get_max_book():
            self.borrowed_books.append(book)
            print(f"{self.name}借阅了{book.title}")
            return True
        else:
            print("你借阅的书籍数量已达到上限")
            return False

    # 定义一个返回书籍的方法
    def return_book(self, book: Book):
        # 如果书籍在借阅列表中,则将其移除并返回True,否则返回False
        if book in self.borrowed_books:
            self.borrowed_books.remove(book)
            return True
        else:
            print("你并未借阅此书")
            return False

    def get_borrowed_books(self):
        # 如果会员没有借阅任何书籍,则输出提示信息
        if len(self.borrowed_books) == 0:
            print("你还没有借阅任何书籍。")
        else:
            print(f"{self.name}借阅的书籍:")
            # 遍历会员的借阅记录,输出书籍信息
            for book in self.borrowed_books:
                print(f"书籍编号:{book.book_id},书名:{book.title},作者:{book.author}")

🔍 代码大白话拆解与科普:

  • Member(ABC) 抽象基类:由于括号里写了 ABC,说明 Member 变成了一个"概念规范类"。它的目的是作为所有会员的亲生老爸,在系统里你不能直接去实例化一个模糊的 Member(比如你不能执行 m = Member(...),Python 会直接报错拦截)。
  • 装饰器 @abstractmethod:这标志着 get_max_book() 是一个抽象方法。老爸对所有儿子说:"我不知道你们各自的借书上限是怎么算的,所以我把这个方法空着(pass)。但我命令你们,凡是继承我的子类,必须把这个方法的具体算账逻辑给我补齐了,不写就让你程序报错跑不起来!"
  • 灵魂多态判定 self.get_max_book():仔细看 borrow_book 里的那句 if len(...) < self.get_max_book():。这一句是全人类面向对象编程的精髓! 父类在写核心借书业务时,根本不用管未来会有多少种会员。它只需要闭着眼睛调用 self.get_max_book()。当普通会员借书时,这一句自动变成检查固定限制;当 VIP 借书时,这一句自动变成按等级计算限制。这就是多态的雏形------父类搭好核心框架,子类决定具体细节。

2.2 增量派生"普通会员"与"VIP 会员"

有了契约老爸,我们现在给它派生出两个具体的会员。请在 Member 类的正下方,紧接着敲下:

ruby 复制代码
class NormalMember(Member):
    def __init__(self, card_no, password, name):
        # 继承父类的构造函数
        super().__init__(card_no, password, name)

    # 实现抽象方法,返回普通会员的借阅上限
    def get_max_book(self):
        return BASE_BORROW_LIMIT


class VipMember(Member):
    def __init__(self, card_no, password, name, level):
        # 继承父类的构造函数
        super().__init__(card_no, password, name)
        # 初始化会员等级
        self.level = level

    # 实现抽象方法,返回会员的借阅上限
    def get_max_book(self):
        return BASE_BORROW_LIMIT + self.level

🔍 代码大白话拆解与科普:

  • class NormalMember(Member) 继承:括号里写了 Member,代表它认领了父类。子类不需要再辛辛苦苦去写一遍名字、卡号、借书还书的方法。老爸的所有本事,它一句话就全自动学会了。这在开发里叫代码复用,能帮程序员省下大量抄代码的时间。
  • super().__init__(...):这一句的意思是"呼叫老爸的构造函数"。当创建普通会员或 VIP 时,把卡号、密码、姓名直接扔给父类的构造函数去初始化,子类省时省力。
  • 属性扩展(self.level = level):VIP 毕竟是 VIP,他有普通人没有的特权。所以 VipMember 除了继承老爸的基础底子外,还自己增量扩展了一个独有的属性 level

⚙️ 见证成就感时刻(第二次命令行测试)

老规矩,我们在文件最底部敲入以下临时测试块,看看借书拦截功能起效了没有:

css 复制代码
if __name__ == "__main__":
    book_test = Book("B001", "Python编程", "Eric", 10, 10)
    normal_man = NormalMember("N10001", "123456", "张三")
    vip_man = VipMember("V10002", "456789", "李四", level=1) # 上限 3+1=4本

    print("--- 测试张三(普通限制3本) ---")
    for _ in range(4): normal_man.borrow_book(book_test)

    print("\n--- 测试李四(VIP1限制4本) ---")
    for _ in range(4): vip_man.borrow_book(book_test)

保存文件,打开终端,再次敲入命令行并回车:

复制代码
python library_system.py

观察控制台输出:

--- 测试张三 ---

张三借阅了Python编程 (成功3次)

你借阅的书籍数量已达到上限 (第4次被老爸类完美拦截!)

--- 测试李四 ---

李四借阅了Python编程 (李四成功借到了4次!)

这就是面向对象的第二和第三个魔法:【继承】与【多态】。子类继承了老爸的代码,同时父类的同一套规则(borrow_book)在面对不同对象时,展现出了不同的逻辑形态。

⚠️ 测试完后,再次把最底部的测试块删掉,我们要盖最后一层------大管家类!


🧭 第三关:搭建系统的"大管家" ------ 统筹联动与真实数据存储

书和人都准备好了,现在我们需要最后一个类 Library,作为大管家把所有东西装进来,并实现真正的文件交互。

3.1 准备真实的 JSON 数据文件

为了让接下来的代码不报错,请在你的代码文件同级目录下,创建一个名叫 json 的文件夹,并在里面新建两个文本文件:

  1. 新建 json/members.json 并写入以下真实的会员数据:
css 复制代码
[  { "card_no": "N10001", "password": "123456", "name": "张三" },  { "card_no": "V10002", "password": "456789", "name": "李外", "level": 1 }]
  1. 新建 json/books.json 并写入以下真实的图书数据:
css 复制代码
[  { "book_id": "B001", "title": "Python编程:从入门到实践", "author": "Eric Matthes", "total_quantity": 10, "available_quantity": 10 },  { "book_id": "B002", "title": "算法导论", "author": "Thomas H. Cormen", "total_quantity": 10, "available_quantity": 10 },  { "book_id": "B003", "title": "计算机网络:自顶向下方法", "author": "James F. Kurose", "total_quantity": 10, "available_quantity": 2 }]

3.2 敲入大管家的初始化与数据加载

请在刚才两个会员子类的正下方,紧接着敲入大管家 Library 类的代码:

python 复制代码
class Library:
    def __init__(self):
        # 初始化会员和书籍列表
        self.members = {}
        self.books = {}
        self.current_member: Member | None = None

        self.load_members()
        self.load_books()

    def load_members(self):
        # 从json文件中加载会员和书籍信息
        with open("json/members.json", "r", encoding="utf-8") as f:
            members = json.load(f)
            for member in members:
                if member["card_no"].startswith("V"):
                    # 根据会员卡号的前缀判断会员类型,并创建相应的会员对象
                    self.members[member["card_no"]] = VipMember(
                        member["card_no"],
                        member["password"],
                        member["name"],
                        member["level"],
                    )
                elif member["card_no"].startswith("N"):
                    # 如果会员卡号以N开头,则创建普通会员对象
                    self.members[member["card_no"]] = NormalMember(
                        member["card_no"], member["password"], member["name"]
                    )

    def load_books(self):
        # 从json文件中加载书籍信息
        with open("json/books.json", "r", encoding="utf-8") as f:
            books = json.load(f)
            # 将书籍信息转换为Book对象,并添加到书籍列表中
            for book in books:
                self.books[book["book_id"]] = Book(
                    book["book_id"],
                    book["title"],
                    book["author"],
                    book["total_quantity"],
                    book["available_quantity"],
                )

    def show_all_books(self):
        print("【所有书籍】")
        for book in self.books.values():
            print(book)

🔍 代码大白话拆解与科普:

  • self.current_member: Member | None 类型注解:这行代表当前正在登录系统的会员。后面的 Member | None 是现代 Python 的标准写法,意思是:这个变量要么是空的(None),要么就必须是一个符合 Member 规范的会员对象。这能让你的代码极具工程严谨性。
  • 简单工厂模式思想(card_no.startswith):看 load_members 内部!大管家在读取死的 JSON 数据时,通过判断卡号的前缀('V' 还是 'N'),在程序运行时动态决定是在内存里实例化一个 VipMember 还是 NormalMember。数据是死的,但在加载的一瞬间,它们全都被赋予了不同的生命和特权!
  • 对封装的无缝调用:在 show_all_books 里,大管家遍历并直接 print(book)。由于我们在第一关写好了 __str__ 魔术方法,这里大管家闭着眼睛就能打印出格式规整的图书列表。

3.3 增量敲入核心业务逻辑(登录/借书/还书)

不要另起新行,请紧接着在刚才 show_all_books 的屁股后面,把借书和还书的协同功能补全:

python 复制代码
    def login(self):
        print("【登录】")
        while True:
            card_no = input("请输入你的会员卡号:")
            password = input("请输入你的密码:")

            # 验证会员卡号和密码
            if card_no not in self.members:
                print("会员卡号不存在,请重新输入。")
                continue

            if self.members[card_no].password != password:
                print("密码错误,请重新输入。")
                continue

            # 当前会员信息
            self.current_member = self.members[card_no]
            print(f"\n欢迎你,{self.current_member.name}!")
            return True

    # 借阅书籍
    def borrow_book(self):
        book_id = input("请输入书籍编号:")

        # 验证书籍是否存在
        if book_id not in self.books:
            print("书籍不存在,请重新输入。")
            return False

        available_quantity = self.books[book_id].get_available_quantity()
        # 验证书籍是否可借
        if available_quantity == 0:
            print("书籍已被借完,请重新输入。")
            return False

        # 🤝 【高内聚低耦合的多态协同】
        # 借阅成功,调用对应会员类定义的借阅数据方法,并减少书籍的余量
        if self.current_member.borrow_book(self.books[book_id]):
            self.books[book_id].set_available_quantity(available_quantity - 1)
            return True

    # 归还书籍
    def return_book(self):
        book_id = input("请输入书籍编号:")

        # 验证书籍是否存在
        if not self.books.get(book_id):
            print("你并未借阅此书!")
            return False

        available_quantity = self.books[book_id].get_available_quantity()
        # 归还成功,调用对应会员类定义的归还数据方法,并增加书籍的余量
        if self.current_member.return_book(self.books[book_id]):
            self.books[book_id].set_available_quantity(available_quantity + 1)
            print(f"书籍编号:{book_id},书名:{self.books[book_id].title},作者:{self.books[book_id].author}归还成功!")
            return True

🔍 代码大白话拆解与科普:

  • 跨对象协同与解耦(全书最核心亮点):注意看 borrow_book 里的这句:if self.current_member.borrow_book(...)
    大管家类自己并没有去写复杂的"这个会员到底能不能借这本书"的逻辑。大管家非常聪明,它直接把书籍对象作为参数,甩给了当前登录的会员对象:"你看看你自己的额度,你觉得能借吗?"
    这就叫各司其职(低耦合)。会员自己核对完毕说可以(返回 True),大管家才在外面调用图书类封装好的 set_available_quantity 把库存减一。大管家、会员、图书三个完全独立的类,在这一瞬间完成了精妙的配合!

3.4 增量敲入系统控制台菜单

紧接着在刚才 return_book 的下方,敲入整个项目的交互菜单。我们使用了 Python 3.10+ 最整洁的 match-case 语法:

python 复制代码
    # 开始运行
    def run(self):
        if self.login():
            print("""
                  
                    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
                    # 1. 借阅书籍  2. 归还书籍  3.查看所有书籍  4.查看借阅记录  5.退出系统  #
                    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
                  
                """)

            while True:
                choice = input("请输入你的 choice:")

                match choice:
                    # 借阅书籍
                    case "1":
                        self.show_all_books()
                        self.borrow_book()

                    # 归还书籍
                    case "2":
                        self.current_member.get_borrowed_books()
                        self.return_book()

                    # 查看所有书籍
                    case "3":
                        self.show_all_books()

                    # 查看借阅记录
                    case "4":
                        self.current_member.get_borrowed_books()

                    # 退出系统
                    case "5":
                        print("退出系统成功!")
                        exit()

                    case _:
                        print("输入错误,请重新输入。")
                        continue

🔍 代码大白话拆解与科普:

  • match-case 语法:这是 Python 3.10 引入的现代控制流语法(类似于其他语言的 switch-case)。相比于以前写满屏幕的 if-elif-elif-else,它结构更清晰、可读性极高。最后的 case _: 代表通配符,用来拦截所有不在 1~5 范围内的错误输入。
  • 多态的终极展现:在选项 4 case "4": self.current_member.get_borrowed_books() 中,无论当前登录的人是普通卡张三还是 VIP 卡李四,大管家都只负责调用这一行相同的命令。Python 会在运行期间自动找到它们各自子类的具体方法去执行。

🏁 终点站:全线通电,启动系统!

现在,你的整个脚本文件已经一气呵成、全部无缝连成一体了。请在整份文件的最底部,敲下这最后的四行启动入口:

ini 复制代码
if __name__ == "__main__":
    # 创建图书馆对象
    library = Library()
    library.run()

🚀 最后的完整命令行大测试

保存你的文件。打开终端,再次敲入终极运行命令:

复制代码
python library_system.py

在终端里跟着提示输入:

  1. 会员卡号输入 N10001,密码输入 123456 登录张三。
  2. 输入选择 1 借阅书籍,然后输入书籍编号 B001
  3. 借阅成功后,再输入选择 3 查看所有书籍。

你会神奇地在命令行控制台里看到:B001 这本书的 余量 真的由 10 本动态变成了 9 本!这就证明你的系统已经大功告成、完美闭环。


🎯 总结

跟着这篇文章从头敲到尾,你不仅仅是在本地拥有了一个能进行文件交互的图书管理 Demo,更重要的是你搞懂了面向对象的终极意义:

  1. 封装:Book 锁住库存数量,提供 Getter/Setter,数据安全得到保障。
  2. 继承:各种会员利用 super() 复用父类逻辑,彻底消灭重复代码。
  3. 多态:大管家闭着眼睛下达统一指令,Python 会自动根据对象实际的类型去执行具体的逻辑。

面向对象编程从来不是条条框框,它是当你面对真实复杂的业务数据时,帮你梳理逻辑、让你少写重复代码、少加班的终极武器。

如果你在用命令行运行或者敲代码的过程中遇到了任何报错,欢迎把你的报错信息贴在评论区,我们一起在线 debug!

相关推荐
林泽毅3 小时前
PyTRIO快速入门(一):概念、推理与训练
人工智能·python·深度学习·机器学习·语言模型
admin and root3 小时前
「移动安全」安卓APP 反编译&frida脱壳技巧分享
android·开发语言·python·web安全·微信小程序·移动安全·攻防演练
雪的季节4 小时前
Python基础5-18
开发语言·python
学术小李4 小时前
基于Pytorch,如何用CUDA自己写算子?(一)
人工智能·pytorch·python
ShirleyWang0125 小时前
让headlamp控制台能访问
linux·服务器·python·k8s·k3s
想会飞的蒲公英5 小时前
用 Streamlit 给文本分类模型做一个演示页面
人工智能·python·机器学习
老徐聊GEO5 小时前
亲测有效的AI品牌检测公司案例分享
大数据·人工智能·python
华研前沿标杆游学5 小时前
2026年企业对标学习TOP7项目:小米数字化考察上榜
python
大模型码小白6 小时前
Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
java·人工智能·python·spring