很多人学面向对象(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 用 cmd 或 PowerShell,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 的文件夹,并在里面新建两个文本文件:
- 新建
json/members.json并写入以下真实的会员数据:
css
[ { "card_no": "N10001", "password": "123456", "name": "张三" }, { "card_no": "V10002", "password": "456789", "name": "李外", "level": 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
在终端里跟着提示输入:
- 会员卡号输入
N10001,密码输入123456登录张三。 - 输入选择
1借阅书籍,然后输入书籍编号B001。 - 借阅成功后,再输入选择
3查看所有书籍。
你会神奇地在命令行控制台里看到:B001 这本书的 余量 真的由 10 本动态变成了 9 本!这就证明你的系统已经大功告成、完美闭环。
🎯 总结
跟着这篇文章从头敲到尾,你不仅仅是在本地拥有了一个能进行文件交互的图书管理 Demo,更重要的是你搞懂了面向对象的终极意义:
- 封装:
Book锁住库存数量,提供 Getter/Setter,数据安全得到保障。 - 继承:各种会员利用
super()复用父类逻辑,彻底消灭重复代码。 - 多态:大管家闭着眼睛下达统一指令,Python 会自动根据对象实际的类型去执行具体的逻辑。
面向对象编程从来不是条条框框,它是当你面对真实复杂的业务数据时,帮你梳理逻辑、让你少写重复代码、少加班的终极武器。
如果你在用命令行运行或者敲代码的过程中遇到了任何报错,欢迎把你的报错信息贴在评论区,我们一起在线 debug!