尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python面向对象编程实战:从类与对象到封装继承多态

Python面向对象编程实战:从类与对象到封装继承多态 1. 项目概述为什么面向对象是Python进阶的必经之路如果你已经学完了Python的基础语法能写一些脚本处理数据、爬取网页但总感觉代码写长了就变得混乱不堪函数和变量散落各处想改个功能得翻半天那么你正站在一个关键的分水岭上。这个分水岭的一边是“脚本小子”式的编程代码能跑但脆弱、难维护另一边则是通往“精英开发者”的道路其核心基石就是面向对象编程。很多人对“面向对象”这四个字望而生畏觉得它抽象、复杂是那些高级程序员才玩的东西。但我想告诉你的是在Python里面向对象不仅不神秘反而是让代码变得清晰、强大和优雅的最实用工具。它不是什么高深理论而是一套组织代码的思维方式就像你用文件夹分类管理文件一样自然。我见过太多学习者卡在这里知道class关键字但不知道何时该用能背出“封装、继承、多态”三大特性但写代码时完全用不上甚至因为初期不当的使用反而把简单问题复杂化从此对面向对象敬而远之。这太可惜了。实际上Python的面向对象设计得非常灵活和“Pythonic”它不是为了增加复杂度而是为了管理复杂度。当你需要建模现实世界中的一个实体比如一个用户、一件商品、一次交易或者当你发现有一组数据和操作这些数据的函数总是绑在一起出现时就是面向对象登场的最佳时机。本内容的目标就是充当你的“保姆”和“向导”彻底拆解Python面向对象编程。我们不空谈概念而是从“为什么需要”出发通过大量贴近实战的案例手把手带你经历从定义一个简单的类到运用继承构建层次再到利用多态编写灵活代码的全过程。我会分享那些官方文档里不会写的“踩坑”心得比如什么时候该用类属性而不是实例属性如何设计类的接口才能让后续协作更顺畅以及那些看似高级的魔法方法__init__、__str__、__call__到底在什么场景下能发挥奇效。无论你是零基础想系统学习还是有一定经验但未能融会贯通相信这篇内容都能帮你打通任督二脉真正将面向对象转化为你代码工具箱中的利器。2. 核心概念破冰从“东西”和“行为”理解类与对象让我们暂时忘掉那些教科书定义。你可以把类想象成一个产品的设计图纸而对象则是根据这张图纸生产出来的具体产品。图纸定义了产品应该有哪些属性比如手机的型号、颜色、内存和能执行哪些功能比如开机、打电话、拍照。但图纸本身不能打电话你必须根据图纸造出一部具体的手机对象才行。在Python里这个过程就是“实例化”。2.1 第一个类的诞生从需求到代码假设我们要开发一个简单的图书馆管理系统书是一个核心实体。用面向对象的思维我们首先想的不是“我要写一个函数来添加书名”而是“书”这个东西应该有什么样的描述和动作class Book: 一个表示书籍的类 def __init__(self, title, author, isbn): 初始化方法在创建对象时自动调用。 Args: title (str): 书名 author (str): 作者 isbn (str): 国际标准书号用于唯一标识书籍 self.title title self.author author self.isbn isbn self.is_borrowed False # 默认状态未借出 def borrow(self): 借书方法 if self.is_borrowed: print(f《{self.title}》已被借出无法再借。) else: self.is_borrowed True print(f你已成功借阅《{self.title}》。) def return_book(self): 还书方法 if not self.is_borrowed: print(f《{self.title}》未被借出无需归还。) else: self.is_borrowed False print(f《{self.title}》已归还感谢) def get_info(self): 获取书籍信息 status 已借出 if self.is_borrowed else 在馆 return f《{self.title}》- {self.author} (ISBN: {self.isbn}) - 状态: {status}逐行解析与核心要点class Book:这行代码定义了一个名为Book的类。类名通常采用驼峰命名法每个单词首字母大写。def __init__(self, title, author, isbn):这是构造方法是类中最特殊也最重要的方法之一。当你执行book1 Book(Python核心编程, Wesley Chun, 978-7-115-48789-0)时Python会自动调用这个方法。self参数这是理解Python面向对象的关键。它代表实例对象本身。在方法内部我们通过self.title来访问或设置这个特定对象的title属性。你可以把self理解为“我自己的”。其他参数title,author,isbn是创建对象时必须提供的信息。self.title title这行代码将传入的title参数值赋值给这个对象的title属性。从此这本具体的书就有了自己的书名。self.is_borrowed False我们给对象添加了一个默认属性表示借出状态。它不需要从外部传入每个新书对象创建时都默认为False。borrow,return_book,get_info方法这些是定义在类内部的函数我们称之为方法。它们描述了“书”这个对象可以执行的动作。注意它们的第一个参数也是self这样方法才能操作调用它的那个具体对象的数据。实操一下# 根据设计图纸Book类生产两本具体的书对象 book1 Book(Python核心编程, Wesley Chun, 978-7-115-48789-0) book2 Book(流畅的Python, Luciano Ramalho, 978-7-121-33059-4) # 每本书都是独立的个体 print(book1.get_info()) # 《Python核心编程》- Wesley Chun (ISBN: 978-7-115-48789-0) - 状态: 在馆 print(book2.get_info()) # 《流畅的Python》- Luciano Ramalho (ISBN: 978-7-121-33059-4) - 状态: 在馆 # book1被借出不影响book2 book1.borrow() # 你已成功借阅《Python核心编程》。 print(book1.get_info()) # 《Python核心编程》- Wesley Chun (ISBN: 978-7-115-48789-0) - 状态: 已借出 print(book2.get_info()) # 《流畅的Python》- Luciano Ramalho (ISBN: 978-7-121-33059-4) - 状态: 在馆 # book1尝试再次借出 book1.borrow() # 《Python核心编程》已被借出无法再借。核心心得self是连接类蓝图和对象产品的桥梁。在类内部定义方法时你必须习惯将self作为第一个参数通过它来访问和修改当前对象的状态。这是Python面向对象语法的一个硬性规定也是理解其工作方式的起点。2.2 类属性 vs 实例属性分清“共享”与“独有”这是初学者最容易混淆的点之一。上面例子中的title、author、is_borrowed都属于实例属性它们绑定在具体的对象self上每本书都不一样。那什么是类属性呢类属性是属于类本身的所有由这个类创建的对象都共享同一份类属性。一个典型的场景是“计数器”。比如我们想知道图书馆一共创建了多少本Book对象。class Book: # 类属性属于类本身所有对象共享 total_books 0 def __init__(self, title, author, isbn): self.title title self.author author self.isbn isbn self.is_borrowed False # 每创建一个对象就修改类属性 Book.total_books 1 # 注意这里是通过类名.类属性名访问 classmethod def get_total(cls): 类方法用于获取或操作类属性。第一个参数是cls代表类本身。 return cls.total_books # 使用 print(f当前书籍总数{Book.total_books}) # 输出0 b1 Book(书1, 作者1, 001) b2 Book(书2, 作者2, 002) print(f当前书籍总数{Book.total_books}) # 输出2 print(f通过类方法获取{Book.get_total()}) # 输出2 print(f对象b1也能访问但不建议直接改{b1.total_books}) # 输出2访问方式实例属性用self.属性名类属性用类名.属性名在类内部或外部或cls.属性名在类方法内。修改影响修改一个对象的实例属性不影响其他对象。修改类属性则所有对象以及类本身看到的这个属性值都会改变。classmethod装饰器它标记的方法叫类方法第一个参数是cls类本身常用于操作类属性或创建备选构造器。避坑指南千万不要在实例方法里用self.类属性名 新值的方式来修改类属性这并不会修改真正的类属性而是给这个对象新建了一个同名的实例属性从而“遮蔽”了类属性。这会导致数据不一致和难以调试的Bug。正确的做法永远是类名.类属性名 新值或通过类方法修改。3. 三大特性实战封装、继承与多态理解了类和对象的基本构造我们就可以深入面向对象的三大核心特性。它们不是孤立的教条而是为了解决实际编程问题而诞生的强大工具。3.1 封装把细节藏起来把接口露出来封装的核心思想是“高内聚低耦合”。把数据和对数据的操作捆绑在一起内聚同时对外部隐藏对象内部复杂的实现细节只暴露一个清晰、稳定的使用接口耦合度低。在Python中封装主要通过“命名约定”来实现而非强制性的访问控制。class BankAccount: 一个银行账户类演示封装 def __init__(self, owner, initial_balance0): # 以一个下划线开头约定为“受保护的”属性提示外部“请勿直接访问” self._owner owner # 以两个下划线开头Python会进行名称改写实现一定程度的“私有化” self.__balance initial_balance def deposit(self, amount): 存款 if amount 0: print(存款金额必须为正数。) return self.__balance amount print(f成功存入 {amount}元。当前余额{self.__balance}元) def withdraw(self, amount): 取款 if amount 0: print(取款金额必须为正数。) return if amount self.__balance: print(余额不足。) return self.__balance - amount print(f成功取出 {amount}元。当前余额{self.__balance}元) def get_balance(self): 查询余额提供公开的接口 return self.__balance def get_owner(self): 查询户主 return self._owner # 使用 account BankAccount(张三, 1000) account.deposit(500) # 成功存入 500元。当前余额1500元 account.withdraw(2000) # 余额不足。 account.withdraw(800) # 成功取出 800元。当前余额700元 print(f户主{account.get_owner()}) # 户主张三 print(f余额{account.get_balance()}) # 余额700 # 尝试“破坏性”访问不推荐但Python允许你这么做 print(account._owner) # 输出张三 可以访问但约定告诉你别这么做 # print(account.__balance) # 报错AttributeError: BankAccount object has no attribute __balance # Python将__balance重命名了实际变成了 _BankAccount__balance print(account._BankAccount__balance) # 输出700 但强烈不建议这样绕开封装单下划线_开头约定俗成的“保护”成员。它只是一个提示告诉其他程序员“这个属性是内部使用的你别直接从外部改它否则后果自负。” Python解释器不会阻止你访问。双下划线__开头Python会进行“名称改写”将__balance在内部变成_BankAccount__balance。这在一定程度上避免了子类意外重写父类的私有属性也增加了从外部直接访问的难度但并非绝对安全。公开方法像deposit、withdraw、get_balance就是类对外提供的公共接口。使用者只需要知道这些方法能做什么不需要关心余额__balance这个属性是如何存储和计算的。设计心得封装的精髓在于设计良好的接口。在定义类时要反复问自己哪些属性和方法是这个对象的核心状态和功能必须暴露哪些是内部的实现细节应该隐藏起来好的封装能极大提升代码的健壮性和可维护性。例如如果未来我们需要在每次余额变动时记录日志只需要在deposit和withdraw方法内部添加日志代码即可所有调用这些方法的外部代码都无需修改。3.2 继承站在巨人的肩膀上实现代码复用当我们需要创建多个具有相似特征但又有所不同的类时继承就派上用场了。它允许我们定义一个通用父类/基类然后创建更具体子类/派生类的类来继承父类的属性和方法并可以添加或覆盖它们。让我们扩展图书馆系统。除了普通的Book我们可能还有EBook电子书和AudioBook有声书。它们都是“资料”都有标题、作者等共性但也有特殊属性。class LibraryItem: 图书馆资料基类 def __init__(self, title, author, item_id): self.title title self.author author self.item_id item_id self.is_checked_out False def check_out(self): if self.is_checked_out: return f{self.title} 已被借出。 self.is_checked_out True return f成功借出 {self.title}。 def return_item(self): if not self.is_checked_out: return f{self.title} 未被借出。 self.is_checked_out False return f{self.title} 已归还。 def get_info(self): status 已借出 if self.is_checked_out else 在馆 return f{self.title} - {self.author} (ID: {self.item_id}) - 状态: {status} class Book(LibraryItem): # Book继承自LibraryItem 纸质书继承基类并添加特有属性 def __init__(self, title, author, item_id, isbn, pages): # 调用父类的__init__方法来初始化共有属性 super().__init__(title, author, item_id) # 添加子类特有属性 self.isbn isbn self.pages pages # 可以覆盖重写父类方法提供更具体的行为 def get_info(self): base_info super().get_info() # 先获取父类的信息 return f{base_info} | 类型: 纸质书 | ISBN: {self.isbn} | 页数: {self.pages} class EBook(LibraryItem): 电子书 def __init__(self, title, author, item_id, file_size, format): super().__init__(title, author, item_id) self.file_size file_size # 文件大小单位MB self.format format # 格式如 PDF, EPUB def get_info(self): base_info super().get_info() return f{base_info} | 类型: 电子书 | 格式: {self.format} | 大小: {self.file_size}MB # 子类可以定义独有的方法 def download_link(self): return f下载链接https://library.com/download/{self.item_id} # 使用 book Book(Python编程, Author A, B001, 123-456, 350) ebook EBook(Python数据科学, Author B, E001, 5.2, PDF) print(book.get_info()) # 输出Python编程 - Author A (ID: B001) - 状态: 在馆 | 类型: 纸质书 | ISBN: 123-456 | 页数: 350 print(ebook.get_info()) # 输出Python数据科学 - Author B (ID: E001) - 状态: 在馆 | 类型: 电子书 | 格式: PDF | 大小: 5.2MB print(ebook.download_link()) # 下载链接https://library.com/download/E001 # book.download_link() # 报错Book类没有这个方法 # 继承来的通用方法依然可用 print(book.check_out()) # 成功借出 Python编程。 print(book.get_info()) # ...状态: 已借出...super().__init__()这是在子类中调用父类构造方法的标准且推荐的方式。它确保了父类的初始化逻辑得以执行。方法重写子类可以重新定义父类中已有的方法如get_info从而提供更具体或不同的行为。在重写时我们常常先通过super().父类方法()调用父类的实现再添加子类的特有逻辑。isinstance和issubclass这两个内置函数用于检查对象与类、类与类之间的关系在需要处理多种类型对象的代码中非常有用。print(isinstance(book, Book)) # True print(isinstance(book, LibraryItem)) # True (Book是LibraryItem的子类) print(issubclass(EBook, LibraryItem)) # True继承设计原则判断是否该使用继承一个很好的检验方法是“是一个”关系。Book是一个LibraryItemEBook也是一个LibraryItem这符合继承的语义。反之如果只是为了复用代码而强行继承可能会导致脆弱的类层次结构。优先使用组合将一个类作为另一个类的属性而非继承是更灵活的设计选择。3.3 多态同一接口不同实现多态是继承的孪生兄弟。它指的是不同类型的对象对同一消息方法调用做出不同的响应。这让我们可以编写出更通用、更灵活的代码。继续上面的例子假设图书馆有一个“资料列表”里面既有Book也有EBook。我们想遍历这个列表打印所有资料的信息。如果没有多态我们可能需要写一堆if isinstance(item, Book)... elif isinstance(item, EBook)...的判断。有了多态代码变得异常简洁。class Library: def __init__(self): self.items [] def add_item(self, item): 参数item是LibraryItem或其子类的对象 if isinstance(item, LibraryItem): self.items.append(item) else: print(只能添加LibraryItem类型的资料。) def list_all_items(self): 遍历所有资料打印信息。这里体现了多态的魅力。 for item in self.items: # 我们不需要知道item具体是Book还是EBook # 只需要知道它一定有get_info这个方法 print(item.get_info()) print(- * 50) # 创建图书馆 lib Library() # 添加不同类型的资料 lib.add_item(Book(物理世界, Science, B002, 111-222, 500)) lib.add_item(EBook(数字宇宙, Tech, E002, 10.5, EPUB)) lib.add_item(Book(历史长河, History, B003, 333-444, 400)) lib.add_item(AudioBook(音乐之旅, Artist, A001, 120, MP3)) # 假设我们后来增加了AudioBook类 # 统一处理 lib.list_all_items() # 输出 # 物理世界 - Science (ID: B002) - 状态: 在馆 | 类型: 纸质书 | ISBN: 111-222 | 页数: 500 # 数字宇宙 - Tech (ID: E002) - 状态: 在馆 | 类型: 电子书 | 格式: EPUB | 大小: 10.5MB # 历史长河 - History (ID: B003) - 状态: 在馆 | 类型: 纸质书 | ISBN: 333-444 | 页数: 400 # 音乐之旅 - Artist (ID: A001) - 状态: 在馆 | 类型: 有声书 | 时长: 120分钟 | 格式: MP3Library类的list_all_items方法根本不在乎item的具体类型。它只依赖于一个约定item对象必须有一个get_info()方法。只要Book、EBook、AudioBook都实现了这个方法无论实现细节如何代码就能正确工作。这就是多态——接口一致实现各异。多态的价值多态极大地提高了代码的可扩展性。未来如果要新增一种资料类型比如Magazine杂志我们只需要让Magazine类继承LibraryItem并实现get_info方法那么Library类的list_all_items方法完全不需要修改就能处理新类型。这是面向对象设计追求的核心目标之一对扩展开放对修改关闭。4. 高级特性与魔法方法让类更“Pythonic”Python的面向对象提供了许多“语法糖”和特殊方法俗称“魔法方法”它们以双下划线开头和结尾如__init__。熟练使用它们能让你的类行为更符合Python惯例用起来更顺手。4.1__str__与__repr__定义对象的字符串表示这是最常用的魔法方法之一。print(对象)或str(对象)时会调用__str__在交互式环境直接输入对象名或使用repr(对象)时会调用__repr__。__repr__的目标是产生一个明确的、通常是合法的Python表达式字符串理想情况下能用来重新创建这个对象。class Point: def __init__(self, x, y): self.x x self.y y def __str__(self): 为用户提供友好的字符串表示 return fPoint({self.x}, {self.y}) def __repr__(self): 为开发者提供明确的、可复现的字符串表示 return fPoint({self.x}, {self.y}) # 这里和__str__一样但通常更详细 # 更标准的__repr__可能像这样return fPoint(x{self.x}, y{self.y}) p Point(3, 4) print(p) # 输出Point(3, 4) (调用 __str__) print(str(p)) # 输出Point(3, 4) (调用 __str__) print(repr(p)) # 输出Point(3, 4) (调用 __repr__) # 在交互式环境中输入 p 会显示Point(3, 4)4.2__len__与__getitem__让对象表现得像容器如果你的类在逻辑上是一个集合比如我们自定义的一个书单类实现这些方法可以让它支持len()函数和下标[]操作。class Bookshelf: def __init__(self): self._books [] def add_book(self, book): self._books.append(book) def __len__(self): 支持 len(bookshelf) 操作 return len(self._books) def __getitem__(self, index): 支持 bookshelf[index] 操作支持切片 return self._books[index] def __str__(self): return fBookshelf with {len(self)} books. shelf Bookshelf() shelf.add_book(Book(A, A1, 001)) shelf.add_book(Book(B, B1, 002)) shelf.add_book(Book(C, C1, 003)) print(len(shelf)) # 输出3 print(shelf[1].title) # 输出B (因为__getitem__返回了Book对象) print(shelf[0:2]) # 输出[__main__.Book object ..., __main__.Book object ...] (切片也支持) for book in shelf: # 实现了__getitem__对象就支持迭代了 print(book.title) # 输出 A, B, C4.3property装饰器将方法“伪装”成属性有时一个属性值可能需要经过计算或者设置时需要一些验证。直接暴露一个实例属性可能不够安全或灵活。property装饰器可以让你定义一个“获取”方法像访问属性一样调用它配合属性名.setter可以定义一个“设置”方法。class Circle: def __init__(self, radius): self._radius radius # 使用“保护”属性存储实际数据 property def radius(self): 获取半径。像属性一样访问circle.radius return self._radius radius.setter def radius(self, value): 设置半径。像属性一样赋值circle.radius 10但带有验证逻辑 if value 0: raise ValueError(半径必须为正数) self._radius value property def area(self): 计算面积。这是一个只读属性因为只有getter没有setter return 3.14159 * self._radius ** 2 property def diameter(self): 直径也是只读属性 return 2 * self._radius c Circle(5) print(c.radius) # 5 (调用的是radius的getter方法) print(c.area) # 78.53975 (调用的是area的getter方法) print(c.diameter) # 10 c.radius 10 # 调用的是radius的setter方法 print(c.area) # 314.159 # c.area 100 # 报错AttributeError: can‘t set attribute (area是只读属性) # c.radius -5 # 报错ValueError: 半径必须为正数 (setter中的验证生效)property让接口更加简洁和直观。使用者感觉像是在操作属性而背后却是安全可控的方法逻辑。5. 面向对象设计实战构建一个小型任务管理系统让我们综合运用以上知识设计一个简单的个人任务管理系统。这个系统需要管理任务任务有不同的状态和优先级并且我们可以对任务进行过滤和统计。5.1 核心类设计首先我们设计Task类。一个任务应该有描述、创建时间、优先级、状态等属性以及改变状态等方法。from datetime import datetime from enum import Enum # 使用枚举定义状态和优先级比直接用字符串更清晰、安全 class TaskStatus(Enum): TODO 待办 IN_PROGRESS 进行中 DONE 已完成 class TaskPriority(Enum): LOW 低 MEDIUM 中 HIGH 高 class Task: 任务类 def __init__(self, description, priorityTaskPriority.MEDIUM): self.description description self.priority priority self.status TaskStatus.TODO self.created_at datetime.now() self.completed_at None def start(self): if self.status TaskStatus.TODO: self.status TaskStatus.IN_PROGRESS return f任务‘{self.description}’已开始。 else: return f任务‘{self.description}’当前状态为{self.status.value}无法开始。 def complete(self): if self.status in [TaskStatus.TODO, TaskStatus.IN_PROGRESS]: self.status TaskStatus.DONE self.completed_at datetime.now() return f任务‘{self.description}’已完成 else: return f任务‘{self.description}’已经是{self.status.value}状态。 def __str__(self): return f[{self.status.value}] {self.description} (优先级: {self.priority.value}, 创建于: {self.created_at.strftime(%Y-%m-%d %H:%M)}) def __repr__(self): return fTask(description{self.description!r}, priority{self.priority}, status{self.status})5.2 管理类设计TaskManager我们需要一个TaskManager来管理多个任务提供添加、删除、查找、过滤、统计等功能。class TaskManager: 任务管理器 def __init__(self): self._tasks [] # 内部使用列表存储任务 def add_task(self, description, priorityTaskPriority.MEDIUM): 添加新任务 task Task(description, priority) self._tasks.append(task) print(f已添加任务{task.description}) return task def get_task_by_index(self, index): 通过索引获取任务简单演示 if 0 index len(self._tasks): return self._tasks[index] return None def filter_tasks(self, statusNone, priorityNone): 过滤任务支持按状态和/或优先级过滤 filtered self._tasks if status is not None: filtered [t for t in filtered if t.status status] if priority is not None: filtered [t for t in filtered if t.priority priority] return filtered def get_stats(self): 获取任务统计信息 total len(self._tasks) done len([t for t in self._tasks if t.status TaskStatus.DONE]) in_progress len([t for t in self._tasks if t.status TaskStatus.IN_PROGRESS]) todo total - done - in_progress return { total: total, done: done, in_progress: in_progress, todo: todo, completion_rate: (done / total * 100) if total 0 else 0 } def list_all(self): 列出所有任务 if not self._tasks: print(当前没有任务。) return for idx, task in enumerate(self._tasks): print(f{idx}: {task}) def __len__(self): return len(self._tasks)5.3 综合使用与演示# 创建管理器 manager TaskManager() # 添加任务 manager.add_task(学习Python面向对象, TaskPriority.HIGH) manager.add_task(写项目周报, TaskPriority.MEDIUM) manager.add_task(整理办公桌, TaskPriority.LOW) manager.add_task(阅读技术文章) print(\n--- 所有任务 ---) manager.list_all() print(\n--- 开始并完成一个任务 ---) task0 manager.get_task_by_index(0) if task0: print(task0.start()) print(task0.complete()) print(\n--- 过滤‘待办’任务 ---) todo_tasks manager.filter_tasks(statusTaskStatus.TODO) for t in todo_tasks: print(t) print(\n--- 过滤‘高优先级’任务 ---) high_pri_tasks manager.filter_tasks(priorityTaskPriority.HIGH) for t in high_pri_tasks: print(t) print(\n--- 任务统计 ---) stats manager.get_stats() for key, value in stats.items(): print(f{key}: {value}) print(f\n总任务数: {len(manager)})这个简单的系统展示了如何用面向对象思维建模问题Task类封装了单个任务的数据和行为TaskManager类负责管理任务集合并提供高层次的操作接口使用Enum使状态和优先级更清晰property虽然没有在这个例子中使用但可以轻松添加比如计算任务耗时和魔法方法__str__、__len__让类的使用更符合直觉。6. 常见陷阱、最佳实践与进阶方向即使理解了概念在实际编码中仍会踩坑。这里分享一些高频问题和经验之谈。6.1 常见陷阱与排查忘记写self参数在类的方法定义中第一个参数必须是self约定俗成也可用其他名字但强烈建议用self。忘记写会导致调用时参数错位。class MyClass: def my_method(param): # 错误缺少self print(param) obj MyClass() obj.my_method(hello) # 会报错TypeError: my_method() takes 1 positional argument but 2 were given # Python会自动把obj作为第一个参数传入相当于 my_method(obj, hello)但函数只定义了一个参数。混淆类属性和实例属性如前所述在实例方法中错误地通过self.类属性名 值来修改类属性实际上会创建实例属性。class Config: setting default def change_setting(self, new_val): self.setting new_val # 错误这创建了实例属性没有修改类属性 c1 Config() c2 Config() c1.change_setting(new) print(c1.setting) # 输出new (实例属性) print(c2.setting) # 输出default (类属性未被修改) print(Config.setting) # 输出default (类属性未被修改)在__init__外初始化可变默认参数这是一个经典的Python陷阱。class BadList: def __init__(self, data[]): # 危险默认参数是可变对象列表 self.data data self.data.append(initialized) b1 BadList() b2 BadList() print(b1.data) # 输出[initialized, initialized] ?! print(b2.data) # 输出[initialized, initialized] ?! # 原因默认参数data在函数定义时就被创建了所有未提供data参数的调用都共享同一个列表。正确做法class GoodList: def __init__(self, dataNone): # 使用None作为默认值 if data is None: self.data [] # 在函数体内创建新的列表 else: self.data data self.data.append(initialized)6.2 面向对象设计最佳实践优先使用组合而非继承“有一个”关系用组合“是一个”关系用继承。组合更灵活耦合度更低。# 组合示例汽车有一个发动机 class Engine: def start(self): print(引擎启动) class Car: def __init__(self): self.engine Engine() # 组合Car对象拥有一个Engine对象 def start(self): self.engine.start() print(汽车启动)保持类和方法小巧单一一个类应该只负责一件事一个方法也应该只完成一个明确的任务。这符合“单一职责原则”让代码更容易理解、测试和维护。多用接口少依赖具体类在高层模块中尽量依赖抽象基类、接口而不是具体的子类。这可以通过我们之前演示的多态来实现提高了系统的可扩展性。善用dataclassPython 3.7对于主要用来存储数据的类dataclass装饰器可以自动生成__init__、__repr__等方法极大减少样板代码。from dataclasses import dataclass from typing import Optional dataclass class Person: name: str age: int email: Optional[str] None # 可选参数默认None p Person(Alice, 30) print(p) # 输出Person(nameAlice, age30, emailNone)6.3 进阶学习方向掌握了面向对象的基础后你可以向这些方向深入设计模式学习如工厂模式、单例模式、观察者模式等经典设计模式它们是解决特定设计问题的成熟模板。可以从《Head First设计模式》或《设计模式可复用面向对象软件的基础》开始。抽象基类ABCPython的abc模块允许你定义抽象基类强制子类实现特定方法实现更严格的接口约束。混入Mixin一种通过多重继承来组合功能的技术用于在不建立严格继承关系的情况下给类添加可复用的功能。描述符Descriptorproperty的背后原理提供了更底层、更强大的属性访问控制机制。元类Metaclass用于创建类的类是Python中最深奥的特性之一常用于框架开发如Django的ORM普通应用开发中很少需要直接使用。面向对象不是Python编程的全部但它是构建中大型、可维护应用程序的骨架。理解它善用它你的代码将从一堆零散的脚本进化成结构清晰、易于协作和扩展的工程作品。最重要的是多写、多重构、多思考。尝试用面向对象的方式去重新设计你以前写过的程序体会其带来的变化这才是最快的成长路径。
返回列表