当前位置:首页>python>《Python 从入门到精通》080|面向对象综合案例:用类重构一个真实小项目

《Python 从入门到精通》080|面向对象综合案例:用类重构一个真实小项目

  • 2026-10-11 07:14:00
《Python 从入门到精通》080|面向对象综合案例:用类重构一个真实小项目

天之道,损有余而补不足。人之道,损不足以奉有余。

这句话源自《道德经》第七十七章。老子通过观察自然界和社会现象,提出“天道”与“人道”的对立,强调自然规律的公平性与人类社会的不公。

前面这十章,我们一直在拆着学面向对象。

类和对象是什么class 怎么写 实例属性、实例方法怎么理解__init__ 到底干了什么self 到底是谁 类属性、类方法、静态方法有什么区别 封装为什么重要 继承怎么复用代码 多态为什么能让接口统一

如果这些知识点一直分散着看,很多人会有一种感觉:单独一章我都看懂了,但真到自己写项目,脑子里还是有点散,不知道什么时候该用类,什么时候该加方法,什么时候该继承。

所以这一章,我们不再拆知识点,而是做一个完整的小项目,把这一阶段真正串起来。

这次我们做一个很适合入门、又很贴近真实业务的小系统:

图书借阅管理小项目

这个项目不大,但非常适合用面向对象来重构。因为它里面天然就有很多“角色”:

图书 用户 借阅行为 图书馆

而“角色”这两个字,正是面向对象最擅长处理的东西。

这一章你最重要的目标,不是死记代码,而是感受到一件事:

为什么一个稍微真实一点的项目,用类来组织,真的会比一堆零散变量和函数顺很多。

一、先看不用类时,会写成什么样

假设你想做一个最简单的借阅系统。

如果不用类,很多人第一反应可能会写成这样:

book1_name = 'Python 入门'book1_author = '小王'book1_is_borrowed = Falsebook2_name = '数据分析实战'book2_author = '老李'book2_is_borrowed = Falseuser1_name = '张三'user1_borrowed_books = []

然后借书逻辑再单独写函数:

defborrow_book(user_name, borrowed_books, book_name, book_is_borrowed):if book_is_borrowed:        print(f'{book_name} 已经被借走了')else:        borrowed_books.append(book_name)        print(f'{user_name} 成功借阅了 {book_name}')

这时候问题很快就会冒出来。

图书一多,变量会爆炸。 用户一多,也会越来越乱。 图书状态和借阅逻辑是分散的。 谁的数据归谁管,并不清晰。

也就是说,这种写法勉强能跑,但项目一旦稍微变大,代码就会越来越像一堆散落零件。

这正是类最适合出场的时候。

二、先别急着写代码,先识别项目里的对象

做面向对象项目,第一步不是上来就敲 class,而是先问自己:

这个项目里,真正重要的“角色”有哪些

在图书借阅系统里,很容易看出至少有三个核心角色:

图书 用户 图书馆

图书,有书名、作者、是否被借出,还会有借出、归还这些行为。 用户,有姓名、借阅记录,还会有查看借阅清单这种行为。 图书馆,要管理很多本书,也要处理借书、还书、展示图书信息这些动作。

你看,一旦这样去想,面向对象的味道就出来了。

以前你是在想“我要写哪些步骤”。 现在你开始想“这个系统里有哪些角色,各自负责什么”。

这就是典型的对象化思维。

三、第一步,先定义最基础的 Book 类

先从图书对象开始。

一本书最基本应该有什么信息?

书名 作者 借阅状态

它最基础应该会做什么?

展示信息 被借出 被归还

所以我们先写一个 Book 类。

classBook:    total_count = 0def__init__(self, title, author):        self.title = title        self.author = author        self._is_borrowed = False        Book.total_count += 1defshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'书名:{self.title},作者:{self.author},状态:{status}')defborrow(self):if self._is_borrowed:            print(f'《{self.title}》已经被借走了')returnFalseelse:            self._is_borrowed = True            print(f'《{self.title}》借阅成功')returnTruedefreturn_book(self):ifnot self._is_borrowed:            print(f'《{self.title}》当前并没有被借出')returnFalseelse:            self._is_borrowed = False            print(f'《{self.title}》归还成功')returnTrue    @classmethoddefshow_total_count(cls):        print(f'当前系统中一共有 {cls.total_count} 本图书')

这一段代码里,已经把前面好几章的知识点真正串起来了。

__init__ 用来初始化图书对象。self.title、self.author 是实例属性。show_info()、borrow()、return_book() 是实例方法。total_count 是类属性,用来统计总图书数。show_total_count() 是类方法,用来展示整个类层面的共享信息。_is_borrowed 前面加下划线,是在表达:这是内部状态,外部最好不要直接乱改。

你会发现,一旦换成类写,图书这个角色立刻就完整了。

四、Book 类为什么比散乱变量更像“真实对象”

这一步特别值得你感受一下。

以前你可能只有:

book_name = 'Python 入门'book_author = '小王'book_is_borrowed = False

这些变量是散的。 而现在:

一本书的名字 一本书的作者 一本书的借阅状态 一本书该怎么借 一本书该怎么还

全都被组织到 Book 这个对象里了。

这正是封装的力量。

外部不需要知道 _is_borrowed 具体怎么变化。 它只需要通过:

book.borrow()book.return_book()

这些公开入口去操作就行。

这比让外部直接写:

book._is_borrowed = True

要稳得多,也自然得多。

五、第二步,再定义 User 类

接下来轮到用户对象。

一个用户最基本应该有什么?

姓名 借到的书

它又应该会做什么?

查看自己的借阅清单

所以可以写出下面这个类:

classUser:def__init__(self, name):        self.name = name        self.borrowed_books = []defshow_borrowed_books(self):ifnot self.borrowed_books:            print(f'{self.name} 当前还没有借任何书')else:            print(f'{self.name} 当前借阅的图书有:')for book in self.borrowed_books:                print(f' - 《{book.title}》')

这段代码虽然不长,但已经很清晰了。

用户对象自己保存自己的借阅记录。 用户对象自己知道怎么展示借阅清单。

你会发现,这种写法的最大好处之一就是:

谁的数据归谁管,越来越清楚。

书的数据归 Book 管。 用户的数据归 User 管。 不是所有东西都挤在全局变量里。

六、第三步,定义 Library 类,专门负责“系统管理”

现在图书和用户都有了,但还有一个问题:

借书和还书这种动作,到底由谁来协调

表面上看,借书既和图书有关,也和用户有关。 所以更自然的做法是,再引入一个“图书馆”角色,专门负责整个系统层面的管理工作。

它需要做的事可能包括:

添加图书 展示馆藏 处理借书 处理还书

所以可以定义一个 Library 类。

classLibrary:def__init__(self, name):        self.name = name        self.books = []defadd_book(self, book):        self.books.append(book)        print(f'图书馆新增图书:《{book.title}》')defshow_books(self):        print(f'欢迎来到 {self.name},当前馆藏如下:')for book in self.books:            book.show_info()defborrow_book(self, user, book):if book notin self.books:            print(f'《{book.title}》不在当前图书馆馆藏中')returnif book.borrow():            user.borrowed_books.append(book)            print(f'{user.name} 已将《{book.title}》加入借阅列表')defreturn_book(self, user, book):if book notin user.borrowed_books:            print(f'{user.name} 并没有借阅《{book.title}》')returnif book.return_book():            user.borrowed_books.remove(book)            print(f'{user.name} 已完成《{book.title}》归还')

这时候,整个小项目的结构就开始非常清楚了。

Book 管一本书自己的状态。User 管一个用户自己的借阅清单。Library 负责协调图书和用户之间的借阅关系。

这就是面向对象特别擅长的地方:

不是把所有逻辑糊成一团,而是让每个角色各管一摊。

七、完整跑一遍,你会更能体会“结构感”

下面我们把整个系统串起来运行一下。

book1 = Book('Python 入门', '小王')book2 = Book('数据分析实战', '老李')book3 = Book('自动化办公指南', '阿明')user1 = User('张三')user2 = User('李四')library = Library('阳光图书馆')library.add_book(book1)library.add_book(book2)library.add_book(book3)print()library.show_books()print()library.borrow_book(user1, book1)library.borrow_book(user2, book1)library.borrow_book(user2, book2)print()user1.show_borrowed_books()user2.show_borrowed_books()print()library.show_books()print()library.return_book(user1, book1)library.return_book(user2, book3)print()library.show_books()

你不一定非要把输出背下来,但建议你自己脑子里过一遍流程。

图书被加入图书馆 图书馆展示馆藏 用户借书 如果书已经借出,就不能重复借 用户查看自己的借阅记录 用户归还图书 图书馆再展示一次最新状态

这时候你会明显感觉到:

整个项目像一个“小系统”了。 而不是几个孤零零的函数拼在一起。

八、为什么这个项目特别适合用类来重构

因为它里边天然存在多个“角色”。

图书不是一个变量,它是一个独立对象。 用户不是一个字符串,它也是一个独立对象。 图书馆更不是一个函数,它是一个协调多个对象的管理者。

你会发现,越是这种“现实角色很明确”的项目,用类来建模就越自然。

这也是为什么前面我们一直强调:

函数更擅长组织步骤 类更擅长组织角色

到了这个案例里,这句话就完全落地了。

九、现在来加入继承,让结构再进一层

前面这个版本已经能跑,而且已经很像真实小项目了。 但如果你学到这里,就停在“所有图书都一种类型”,那前面的继承和多态就还没真正落地。

现实里,图书馆里的书可能不止一种。

有纸质书 有电子书

它们有共同点:

都有书名 都有作者 都能展示信息

但它们也有差异:

纸质书有页数 电子书有文件大小

这时候,就非常适合用继承来表达。

先把 Book 当父类,然后让 PaperBook 和 EBook 继承它。

classBook:    total_count = 0def__init__(self, title, author):        self.title = title        self.author = author        self._is_borrowed = False        Book.total_count += 1defshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'书名:{self.title},作者:{self.author},状态:{status}')defborrow(self):if self._is_borrowed:            print(f'《{self.title}》已经被借走了')returnFalseelse:            self._is_borrowed = True            print(f'《{self.title}》借阅成功')returnTruedefreturn_book(self):ifnot self._is_borrowed:            print(f'《{self.title}》当前并没有被借出')returnFalseelse:            self._is_borrowed = False            print(f'《{self.title}》归还成功')returnTrue

然后定义两个子类:

classPaperBook(Book):def__init__(self, title, author, pages):        super().__init__(title, author)        self.pages = pagesdefshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'纸质书:《{self.title}》,作者:{self.author},页数:{self.pages},状态:{status}')classEBook(Book):def__init__(self, title, author, file_size):        super().__init__(title, author)        self.file_size = file_sizedefshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'电子书:《{self.title}》,作者:{self.author},文件大小:{self.file_size}MB,状态:{status}')

这里就把继承、super()、方法重写都串起来了。

父类 Book 提供共性。 子类 PaperBook 和 EBook 增加自己的特有属性。 并且重写 show_info(),让同一个接口表现不同效果。

十、这时候,多态就真正落地了

注意 Library.show_books() 这个方法,我们根本不用改。

还是这样写:

defshow_books(self):    print(f'欢迎来到 {self.name},当前馆藏如下:')for book in self.books:        book.show_info()

为什么不用改?

因为不管 book 是 PaperBook 还是 EBook,它们都支持:

book.show_info()

这就是统一接口。

但不同对象执行后,会打印不同结果。 这就是多态。

我们来试一下:

library = Library('阳光图书馆')book1 = PaperBook('Python 入门', '小王', 320)book2 = EBook('数据分析实战', '老李', 15)book3 = PaperBook('自动化办公指南', '阿明', 280)library.add_book(book1)library.add_book(book2)library.add_book(book3)print()library.show_books()

这时候,外部只知道统一调用 show_info()。 至于有的显示页数,有的显示文件大小,那是不同子类自己决定的。

你会发现,这就是前一章讲的多态,在真实项目里非常自然的使用方式。

十一、封装在这个项目里又体现在哪里

很多人学封装的时候,容易停留在“哦,前面加下划线”这个层面。 但这个项目刚好能帮你把封装真正看明白。

比如图书的借阅状态 _is_borrowed。 外部理论上可以直接改,但正常设计下,最好不要这样干。 而应该通过:

book.borrow()book.return_book()

这些方法来改变状态。

这样做的意义在于:

借阅成功还是失败 归还是否合理 状态如何变更

这些规则都由对象自己控制。

这就是封装的价值:

不是暴露内部状态让外部乱改,而是提供合理入口让外部正常操作。

十二、为什么这个项目比过程式写法更容易扩展

这一步特别重要。

如果你一开始是过程式硬写,后来想加“电子书”这种新类型,往往会非常麻烦。因为你很多地方可能都写死了对某种图书结构的判断。

但现在用了类以后,扩展就顺很多。

想加新类型?

新建一个子类就行。

想让某类图书展示方式不同?

重写 show_info() 就行。

想让某类图书借阅规则不一样?

重写 borrow() 就行。

比如以后你还可以写一个“参考书”子类,规定它只能馆内阅读,不能借出:

classReferenceBook(Book):defborrow(self):        print(f'《{self.title}》属于参考书,不能外借')returnFalse

你看,只加一个类,整个系统就获得了新能力。 而图书馆里统一的借书流程几乎不用大改。

这就是面向对象在项目扩展上的优势。

十三、类属性在这个项目里也不是摆设

前面 Book.total_count 看起来像一个顺手加的计数器,但它其实非常实用。

比如你可以直接这样看总图书数:

Book.show_total_count()

或者补一个类方法:

@classmethoddefshow_total_count(cls):    print(f'当前系统中一共有 {cls.total_count} 本图书')

这时候你会更明显理解:

类属性不是某本书自己的信息 而是整个图书类共享的信息

这一章把它放进项目里,就是为了让你真正看到它的用武之地。 不是为了考试记名词,而是为了项目里真的能用上。

十四、静态方法也可以很自然地放进来

前面说过,静态方法最适合做那种“和类有关,但不依赖实例状态,也不依赖类状态”的工具逻辑。

在这个项目里,也很容易找到这样的场景。

比如判断书名是否合法:

classBook:    total_count = 0def__init__(self, title, author):        self.title = title        self.author = author        self._is_borrowed = False        Book.total_count += 1    @staticmethoddefis_valid_title(title):return len(title.strip()) > 0

调用时:

print(Book.is_valid_title('Python 入门'))print(Book.is_valid_title('   '))

你会发现,这个功能和 Book 主题有关,但它既不需要某本具体书的数据,也不需要整个类共享信息。 这就很适合写成静态方法。

这样一来,类属性、类方法、静态方法,就不是零散知识点,而是都在项目里找到位置了。

十五、这个项目真正锻炼你的,不只是 OOP 语法

说到底,这一章最重要的,不只是让你把几个类写出来,而是训练你一种更接近真实开发的思路:

先识别角色 再明确每个角色该负责什么 把共性抽出来 把个性留给子类 把内部状态保护起来 让外部通过统一接口来用

这套思路,你以后做学生管理系统、订单系统、用户系统、记账系统,其实都能用上。

所以这一章虽然写的是图书借阅,但它练的不是“图书”本身,而是“怎么用类去组织一个小项目”。

这才是真正值钱的地方。

十六、再给你一个更完整的整合版代码

下面给你一个更完整、适合自己敲一遍的整合版。

classBook:    total_count = 0def__init__(self, title, author):        self.title = title        self.author = author        self._is_borrowed = False        Book.total_count += 1defshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'书名:{self.title},作者:{self.author},状态:{status}')defborrow(self):if self._is_borrowed:            print(f'《{self.title}》已经被借走了')returnFalse        self._is_borrowed = True        print(f'《{self.title}》借阅成功')returnTruedefreturn_book(self):ifnot self._is_borrowed:            print(f'《{self.title}》当前并没有被借出')returnFalse        self._is_borrowed = False        print(f'《{self.title}》归还成功')returnTrue    @classmethoddefshow_total_count(cls):        print(f'当前系统中一共有 {cls.total_count} 本图书')    @staticmethoddefis_valid_title(title):return len(title.strip()) > 0classPaperBook(Book):def__init__(self, title, author, pages):        super().__init__(title, author)        self.pages = pagesdefshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'纸质书:《{self.title}》,作者:{self.author},页数:{self.pages},状态:{status}')classEBook(Book):def__init__(self, title, author, file_size):        super().__init__(title, author)        self.file_size = file_sizedefshow_info(self):        status = '已借出'if self._is_borrowed else'可借阅'        print(f'电子书:《{self.title}》,作者:{self.author},文件大小:{self.file_size}MB,状态:{status}')classUser:def__init__(self, name):        self.name = name        self.borrowed_books = []defshow_borrowed_books(self):ifnot self.borrowed_books:            print(f'{self.name} 当前还没有借任何书')else:            print(f'{self.name} 当前借阅的图书有:')for book in self.borrowed_books:                print(f' - 《{book.title}》')classLibrary:def__init__(self, name):        self.name = name        self.books = []defadd_book(self, book):        self.books.append(book)        print(f'图书馆新增图书:《{book.title}》')defshow_books(self):        print(f'欢迎来到 {self.name},当前馆藏如下:')for book in self.books:            book.show_info()defborrow_book(self, user, book):if book notin self.books:            print(f'《{book.title}》不在当前图书馆馆藏中')returnif book.borrow():            user.borrowed_books.append(book)            print(f'{user.name} 已将《{book.title}》加入借阅列表')defreturn_book(self, user, book):if book notin user.borrowed_books:            print(f'{user.name} 并没有借阅《{book.title}》')returnif book.return_book():            user.borrowed_books.remove(book)            print(f'{user.name} 已完成《{book.title}》归还')

这套代码并不算长,但它已经很像一个完整的小项目骨架了。

十七、你现在应该重点看懂什么

不是每一行都背下来。 而是重点看懂下面这些关系:

Book 为什么是父类PaperBook 和 EBook 为什么适合继承它show_info() 为什么能体现多态_is_borrowed 为什么体现了封装Library 为什么不适合只是一个普通函数集合User 为什么也应该是独立对象

只要这些关系看明白了,你就不是在机械抄代码,而是真的开始有项目建模能力了。

十八、做完这一章,你会发现面向对象终于“落地”了

前面十章,其实一直都像在搭骨架。

第71章到第75章,是在搭基础骨架。 第76章到第79章,是在补更完整的结构能力。 而这一章,就是把前面的骨架真正装进一个小系统里。

你会突然发现,那些原本看起来有点抽象的词:

类 对象 封装 继承 多态

到了项目里以后,其实都很具体。

不是为了定义而定义。 不是为了显得高级而高级。 而是真的能让代码更整洁、更清楚、更容易扩展。

这就是为什么学面向对象,不能只停留在背概念。 一定要进到项目里,才会真的通。

十九、本章小练习

你可以基于这一章的图书借阅系统,自己做两个升级版练习。

第一个练习,加入“借阅上限”功能。 比如每个用户最多只能借 3 本书。 这会迫使你去思考:这个规则应该放在 User 里,还是放在 Library.borrow_book() 里。

第二个练习,加入“参考书不能外借”功能。 写一个 ReferenceBook 子类,重写 borrow() 方法。 这会帮助你真正把继承和多态用起来。

如果你能自己把这两个功能补进去,这一阶段的内容就算真的吃透了。

二十、本章总结

这一章,我们没有再拆零碎知识点,而是把面向对象这一阶段真正收束成了一个完整的小项目。

你看到了怎么从真实业务里识别对象。 看到了如何用类来组织图书、用户、图书馆这些角色。 看到了封装如何保护内部状态。 看到了继承如何提取共性、减少重复。 看到了多态如何让统一接口在不同对象上表现不同。 也看到了类属性、类方法、静态方法在项目里到底能放在哪里。

到这里,第八阶段就算真正收束了。 你已经不只是会写几个类了,而是开始具备用类去组织一个小型真实项目的能力。

下一章,我们正式进入第九阶段:081|模块是什么:为什么 Python 代码可以拆文件。

最新文章

随机文章