我自己的Python学习走到第六周正好卡在一个很有意思的位置基础语法已经翻来覆去练过好几遍面向对象的说法听是听过可真要自己上手写类、写继承心里还是发虚。这一周我给自己定的目标很简单——把面向对象这条主线彻底理清楚并且用一个小项目把学的东西串起来。如果你也正处在类似阶段这篇内容应该能给你一些参考。先说说这一周到底学了什么类与对象构造方法实例属性和类属性的区别继承、封装、多态三大特征还有几个平时写脚本不怎么注意、但真正做项目时逃不掉的细节比如魔法方法、类方法和静态方法。后面我会把每一块拆开讲清楚配合我实际敲过的代码、踩过的坑以及最后完整做出来的一个图书管理系统。1. 第六周的学习定位从“写脚本”切换到“写程序”前五周基本都在解决“怎么把一件事用代码做出来”变量、流程控制、函数、字符串和列表的处理、文件的读写。这些内容构成了你写代码的“词汇”和“语法”但写出来的东西本质上是一条一条往下走的脚本——一个几十行的脚本从第一行跑到最后一行结束收工。到第六周学习的重心开始切换。你不再只是写一段能运行的逻辑而是要开始考虑“怎么组织代码”。面向对象OOP解决的核心问题就是当代码量上去以后数据和操作数据的方法容易散落得到处都是维护起来非常痛苦。面向对象的设计思路是把相关的数据和操作绑在一起形成一个一个独立的“对象”让代码的结构更接近现实世界的模型。1.1 为什么第六周要集中啃面向对象很多自学教程会把面向对象放在比较靠后的章节但实际项目里你几乎从第一天就会接触到类。比如用某个第三方库你导入的很多东西本身就是类你用requests发送请求拿到的是一个Response对象它身上带着.status_code、.text、.json()这些属性和方法。如果不理解对象的概念你只能死记硬背“返回的东西有哪些方法”而没法理解“为什么它是一个对象”。另外这一周也是从“能跑就行”到“像样项目”的转折点。我自己在写前几周的小练习时函数一套到底全局变量随处可改代码一长就开始乱。到了第六周通过类和对象把数据和操作封装起来之后最直接的感受是逻辑清晰了改一处功能不再担心牵连到其他地方。1.2 这一周的学习目标怎么定我给自己列了四个硬性目标理解类与实例的关系能解释清楚“类”和“对象”到底各自是什么掌握__init__构造方法的写法知道实例属性怎么绑定、怎么访问掌握继承的写法和super()的用法能处理子类调用父类方法的场景理解封装的意义知道用_前缀和 property 来保护内部数据这四个目标不贪多但每个都必须真正动手写过代码才算过。只看不写是这一周最容易掉进去的坑后面我会详细说。2. 核心知识拆解类、对象、属性与方法第六周的内容看似多其实核心就一张图类是模板对象是模板造出来的具体实例。模板里规定了这一类事物都有的属性比如书有书名、作者、价格和行为比如借出、归还而对象就是按照模板造出来的一本具体的书。2.1 构造方法__init__和 self 的用法Python 里定义类的写法很直白class Book: def __init__(self, title, author, price): self.title title self.author author self.price price self.is_borrowed False def borrow(self): if self.is_borrowed: print(f《{self.title}》已被借走) return False self.is_borrowed True print(f《{self.title}》借出成功) return True def return_book(self): if not self.is_borrowed: print(f《{self.title}》没有被借出) return False self.is_borrowed False print(f《{self.title}》归还成功) return True这段代码里最关键的是self。self指代“当前这个对象”。当book1 Book(三体, 刘慈欣, 50)执行时Python 会把book1这个对象自动传给__init__里的self所以self.title就是book1.title。这是新手最容易懵的地方为什么定义方法时第一个参数总是self调用时又不需要传这个参数——因为 Python 在幕后帮你传了。注意self这个名字是约定俗成你换成this或者obj也能跑但千万别换。所有 Python 开发者都默认看到self就知道它指当前实例你改了反而让人看不懂。2.2 类属性与实例属性差别比想象中大很多初学者会把类属性和实例属性搞混。区别其实一句话就能说明白实例属性属于某个具体对象每个对象各存各的类属性属于类本身所有对象共享同一份。class Library: total_books 0 # 类属性 def __init__(self, name): self.name name # 实例属性 Library.total_books 1访问类属性要用类名.属性名Library.total_books。如果你写成self.total_books 0那就变成了在实例上新建了一个同名的实例属性会覆盖类属性而且每个实例各自独立计数——很多入门项目里计数器算不对就是栽在这个细节上。第六周的学习一定要动手试一遍这个区别否则后面写项目遇到的“诡异问题”会越来越多。2.3 三大特征封装、继承、多态封装把数据和操作数据的方法放进同一个类里对外只暴露必要的接口。比如上面的Book类把“状态”“借出”“归还”都装在一起外部调用者不需要关心内部怎么实现只需要调用borrow()和return_book()。这带来的直接好处是如果以后要加一条“借出超过30天不允许再借”只需要改类内部调用方代码一行不用动。继承子类自动拥有父类的属性和方法在此基础上可以扩展或重写。最经典的例子就是定义一个ElectronicBook(Book)它在Book的基础上多一个file_size属性并且重写borrow()方法让电子书可以同时借给多个人。多态同一个方法名在不同类上表现出不同的行为。Book.borrow()和ElectronicBook.borrow()虽然调用方式完全一样但行为不同——这就是多态。写代码时你不需要关心某个对象到底是哪个子类只需要知道它能响应borrow()方法就行了。这三大特征不是面试要背的名词而是解决问题的工具。遇到“多个类有共同的属性和行为”时用继承抽公共部分遇到“不想让外部随便改内部状态”时用封装遇到“一组不同的对象都要执行同样的动作”时用多态。3. 实战项目从零写一个图书管理系统理论看十遍不如动手写一遍。第六周的周末我拿出了大约四个小时把前五天学的东西全部塞进一个小项目里一个控制台版的图书管理系统。麻雀虽小五脏俱全这里完整拆给你看。3.1 项目结构与需求拆解先想清楚要做什么再动手写代码。我的需求是支持添加图书录入书名、作者、价格支持展示图书列表列出所有书和状态支持借书、还书按书名操作支持查询按作者或书名关键字筛选支持统计显示馆藏总数、已借出数量这一步看起来简单但特别重要。我见过很多学习者拿到需求就直接开写写到一半发现逻辑绕回来了又推翻重来。先把需求拆成小的功能点写起来才不迷路。3.2 核心代码实现定义两个类一个Book一个Library。Library负责图书集合的管理Book负责单个图书的状态。class Book: def __init__(self, title, author, price): self.title title self.author author self.price price self.is_borrowed False def __str__(self): status 已借出 if self.is_borrowed else 可借 return f《{self.title}》 {self.author} ¥{self.price} [{status}] class Library: def __init__(self, name): self.name name self.books [] def add_book(self, title, author, price): book Book(title, author, price) self.books.append(book) print(f添加成功{book}) def list_books(self): if not self.books: print(馆内暂时没有图书) return for book in self.books: print(book) def borrow_book(self, title): for book in self.books: if book.title title: if book.borrow(): return return print(f没有找到《{title}》) def return_book(self, title): for book in self.books: if book.title title: book.return_book() return print(f没有找到《{title}》) def search_by_author(self, author): results [book for book in self.books if book.author author] if not results: print(f没有找到 {author} 的作品) for book in results: print(book) def stats(self): total len(self.books) borrowed sum(1 for book in self.books if book.is_borrowed) print(f馆藏 {total} 本已借出 {borrowed} 本可借 {total - borrowed} 本)这里有几个小点可以单独说一下。__str__魔法方法的作用是定义对象的“展示文本”。有了它print(book)就能直接打印出友好信息而不是默认的__main__.Book object at 0x...。这是Python里最常用的魔法方法之一第六周必须掌握。Library类的borrow_book通过书名去匹配然后转调Book.borrow()。这样做的好处是“借书”这个逻辑的细节比如是否已借出、状态怎么改都封装在Book里面Library不需要知道细节只负责找到书。3.3 主程序交互逻辑主程序就是给用户一个菜单循环接收输入调用对应方法def main(): lib Library(我的小书馆) while True: print(\n1. 添加图书) print(2. 图书列表) print(3. 借书) print(4. 还书) print(5. 按作者查询) print(6. 统计信息) print(0. 退出) choice input(请选择) if choice 0: break elif choice 1: title input(书名) author input(作者) price float(input(价格)) lib.add_book(title, author, price) elif choice 2: lib.list_books() elif choice 3: title input(输入要借的书名) lib.borrow_book(title) elif choice 4: title input(输入要还的书名) lib.return_book(title) elif choice 5: author input(输入作者名) lib.search_by_author(author) elif choice 6: lib.stats() else: print(无效选项请重新输入) if __name__ __main__: main()if __name__ __main__这一行值得单独解释。它的意思是只有当你直接运行这个脚本时下面的main()才会执行。如果这个文件被别的代码importmain()不会自动跑。这是一种常见的代码组织方式让同一个文件既能被直接运行也能作为模块被引入。我在学第五周时不太在意这种东西到第六周写稍微像样的项目才发现这是标准做法。3.4 这块代码的改进空间这个小项目完成之后我回过头看发现有些地方还可以继续优化。比如书名查找目前是精确匹配实际使用中用户可能输入“三”就想找到《三体》这就用得上in而不是。另外Book和Library还比较基础如果加上电子书子类用继承来处理“电子书不占实体库存”这种逻辑整个项目就会更像真实系统。第六周做到目前这个程度已经足够达成学习目标优化方向留到后面学得更深入时再慢慢补。4. 边学边踩坑第六周最容易遇到的五个问题这一周我遇到的坑比前面几周加起来都多而且很多坑看起来都是“玄学”——代码明明照着写了结果却不对。后来排查下来其实全都是对某些基础机制理解不透导致的。下面整理的是我亲身踩过的以及身边一起学的朋友问到最多的五个问题。4.1 报错 TypeError: Book() takes no arguments刚写类的时候我确定自己写了__init__但一实例化就报“takes no arguments”。检查了半天发现是__init__拼写成了_init_少了一对下划线。Python把_init_当成普通方法实例化的时候找不到构造方法于是只能调用默认的无参构造。这个坑在新手期出现的频率极高第一反应该去检查魔法方法两侧的下划线数量是否都是两个。4.2 忘了传 self调用方法时报错class Book: def borrow(): # 少了 self print(borrow)然后调用book.borrow()就报错。原因是book.borrow()在Python内部等价于Book.borrow(book)需要一个参数接收实例但你的方法定义是零参数自然报错。这个机制一开始很反直觉多敲几遍再报错时能秒懂问题在哪。4.3 实例属性在类定义时“预声明”踩坑class Book: is_borrowed False # 类属性 book1 Book() book1.is_borrowed True # 实例属性覆盖了类属性这段代码能跑但语义上和你想的可能不一样book1.is_borrowed True看起来像是“修改了类的默认值”实际上它是给book1创建了一个全新的实例属性类属性Book.is_borrowed仍然是False。这种覆盖行为初学者非常容易误判。正确的做法是把可变状态比如是否借出放在__init__里绑定到实例上不要把可变状态放在类属性里。4.4 在__init__里使用可变默认参数这是我见过最经典的Python陷阱没有之一class Library: def __init__(self, books[]): self.books books看起来没问题。但你创建两个Library实例时它们会共享同一个列表——第二个实例添加的书第一个实例也能看到。原因是默认参数在函数定义时只被创建一次所有调用共用同一个列表对象。正确写法是def __init__(self, booksNone): if books is None: books [] self.books books4.5 继承里忘了调用super().__init__()子类定义了__init__之后父类的__init__不会自动执行。如果父类在__init__里初始化了某些属性子类就必须显式调用super().__init__()。class ElectronicBook(Book): def __init__(self, title, author, price, file_size): super().__init__(title, author, price) # 先初始化父类属性 self.file_size file_size如果不写这一行子类实例就没有title和author属性后面访问就报AttributeError。排查的时候经常让人摸不着头脑因为错误信息指向的是访问属性的那一行而真正的问题在构造函数里少了一行调用。4.6 问题排查方法小结遇到问题先读完整的报错信息Python的报错已经告诉你文件名、行号和错误类型了。第六周开始代码变长之后我改用了一个排查习惯把报错信息里的“最后一层”和“最初那层”都打开看很多时候问题出在调用链的源头而不是报错那一行。另外一个习惯是写一个小文件只复现出错的那几行代码把干扰因素全部去掉——这个习惯会伴随整个编程生涯越早建立越好。5. 学习方法与工具建议第六周怎么学效率最高第六周的内容难度比前五周明显上了一个台阶。我自己的感觉是前五周像是学单词和语法第六周要开始学着“造句和写段落”了。这个阶段的学习方法如果不调整很容易陷入“看懂了写不出”的困境。5.1 先画图再写码这是我这一周收获最大的一个习惯。在写图书管理系统之前我先在本子上画了一个简单的结构两个方框一个写着Book一个写着Library用箭头标出Library持有Book的集合旁边备注了各自的方法。画完之后整个程序的结构在脑子里就清晰了写代码时按图索骥出错率大大降低。这个习惯的原理很简单面向对象设计本质上是“建模”而图形化表达能强迫你把事物的属性和行为梳理清楚。哪怕画得很丑也没关系自己看得懂就行。5.2 刻意练习每天三个小类第六周开始我不再只是跟着教程敲代码而是给自己安排了刻意的练习任务每天定义三个类主题随意比如“狗”“汽车”“学生”要求每个类至少有一个属性和两个方法。这样做的好处是让你在没有教程提示的情况下独立构建一个完整的类。刚开始可能会卡壳比如不知道某个属性该放实例还是类但练习几天之后这些东西就开始变成肌肉记忆。练习之后我又加了一步给每个类补一个__str__方法。这一步虽然小但能让你慢慢适应“控制台里看到的信息是代码决定的”这个思维对后面调试很有帮助。5.3 用dir()和help()探索对象这个技巧是我在学习过程中发现很实用的调试手段。当你拿到一个对象不确定它有哪些属性和方法时直接用dir(obj)就能列出来。想知道某个方法怎么用运行help(obj.method)就能看到文档说明。我写代码遇到“这个对象能不能这么调”的疑问时第一反应不是去搜资料而是先用dir()看一眼再验证一遍。这个习惯让你从“死记API”切换到“主动探索”学习速度会快很多。5.4 调试工具的入门使用第六周代码变长print大法开始不够用了。我花了半小时掌握了pdb的最基本用法import pdb def add_book(self, title, author, price): pdb.set_trace() # 程序运行到这里会暂停进入交互式调试 book Book(title, author, price) self.books.append(book)进入调试模式后常用的命令就几个n执行下一行p 变量名打印变量的值c继续运行。我给所有写代码的同学一句建议花一个小时把调试工具的基本用法学会节省的时间会是几十倍。6. 代码规范与组织习惯第六周开始像“项目”而不是“练习”前几周我写的Python脚本都是单文件从上到下跑完就完事。到第六周代码量突破几百行以后我开始意识到代码组织的重要性。这一部分虽然不属于“面向对象”的语法本身但在实际学习体验中占了很大比重。6.1 文件拆分类一个文件主程序一个文件把Book和Library定义在一个文件比如models.py主程序交互逻辑放在另一个文件比如main.py然后用from models import Book, Library引入。这样做的好处很直观以后想扩展功能比如加一个报表模块不需要翻主线代码想测试某个类直接写一个临时脚本引入就行。这个习惯在第六周养成了后面学任何东西都会受益。6.2 命名规范类名用驼峰式BookManager变量名用小写加下划线book_list方法名也用下划线get_all_books。常量用全大写MAX_BORROW_DAYS 30。这些约定不是语法强制但代码是写给人看的规范命名让你一个月后翻回来看自己的代码还能秒懂当时的意思。6.3 写注释的正确姿势我见过两种极端一种是一行代码一行注释把代码弄得稀碎另一种是完全没有注释过两天自己也看不懂。我的做法是在类和复杂方法的开头写两三行说明说清楚“这个类是干嘛的”和“这个方法解决什么问题”具体实现里的难点再单行注释。注释不是每行都解释而是要解释“为什么”——为什么这里要检查状态、为什么这里要调用父类方法。代码自己会说“怎么做”注释负责说“为什么”。6.4 保存好每一周的代码建议从现在开始把每一周的练习和项目都放进单独的文件夹给项目起一个有意义的名字比如week06_library。到后面学Web框架、爬虫或者数据分析时代码量会更大这时候回过头翻看前几周的项目不仅能复习还能看到自己的进步曲线。这个习惯我现在回头看确实很有价值一周前的代码可能已经陌生了但通过项目目录能快速跳回那个学习阶段。7. 第六周进阶面向对象之后再往哪里走如果你的学习进度比我快已经把类、继承、封装、多态都练熟了可以往这几个方向再探一步。这些内容严格来说不完全是第六周的范围但都是自然衔接的下一个节点。7.1 魔法方法的大坑与妙用__str__已经见过了除此之外还有几个出场率极高的魔法方法值得动手写一写__repr__面向开发者的字符串表示调试时打印对象特别好用__eq__定义两个对象“相等”的逻辑比如书名和作者相同时视为同一本书__lt__定义排序逻辑比如让书按价格排序我写过一个小练习给Book加上__eq__和__lt__然后直接对图书列表调用sorted()体验了一把“告诉Python怎么比较Python帮你排序”的感觉。7.2 类方法和静态方法有时候一个方法和具体实例无关比如“从几个参数创建一个图书对象”或者“计算所有图书的平均价格”。这时候用classmethod和staticmethod。它们和普通方法的区别一句话就能说清类方法接收类作为第一个参数静态方法什么都不接收。举一个简单的例子class Book: classmethod def create_default(cls): return cls(未命名, 未知作者, 0) staticmethod def valid_price(price): return price 0前者Book.create_default()创建一本默认书后者Book.valid_price(60)校验价格。两者的调用都不需要先创建实例这就是它们和实例方法的区别。7.3 关联类型用对象组合代替脆弱的继承继承不是万能的有时候对象之间是“组成”而不是“是一种”的关系。比如Library不应该是Book的子类——图书馆并不是一种书但图书馆确实包含很多书。这时候用组合Library里面持有Book对象的列表就非常自然。第六周做图书管理系统时我用到的正是组合。判断用继承还是组合有个简单标准问自己“子类继承父类后是否真的能当成父类来用”。能用继承不能用组合。写在最后的一点体会从第五周写出第一个能用的脚本到第六周完成这个图书管理系统我最大的感受是编程的学习不是一条直线。前几周学语法的时候每天都能看到进步到了面向对象这周连续两天都觉得自己在“倒退”——上课能听懂打开编辑器却不知道从哪起头。但熬过那个阶段把代码敲满一整页之后很多东西突然就通了。如果你也处在第六周前后学得有点吃力我的建议是不要跟别人的进度比。有人一周就能把面向对象玩得飞起有人需要两周时间来消化这段“从脚本到程序”的思维转换都很正常。慢一点没关系关键是你写出来的每一个类、犯过的每一个错都真正变成了自己的东西。最后分享一个小习惯这周开始我把每天遇到的理解不到位的知识点记在一个单独的文件里比如“实例属性和类属性到底是啥区别”记录下来隔几天回来看一遍直到觉得自己能给别人讲明白为止。能不能用自己的话把这个概念说清楚是我判断自己是否真正学会的标尺。你也试试可能比多看两遍教程管用得多。