欢迎来到「Python学习与交流」!在前九课中,我们用函数和模块把零散的代码组织成了工具。但当程序规模继续膨胀,你会发现:数据和操作数据的函数散落在各处,修改一个功能要翻遍整个文件;想复用某段逻辑,复制粘贴后又难以维护。
今天,我们将学习面向对象编程(OOP)。它不是新的语法糖,而是一种全新的思维方式——把数据和对数据的操作打包成独立的“对象”,让代码像现实世界一样自然、可扩展、易协作。这是从“写脚本”迈向“做工程”的关键一步。
一、类与对象:万物皆对象的起点
- 类(Class):对象的蓝图或模板,定义了这类事物共有的属性和行为。
- 对象(Object):根据类创建的具体实例,每个对象拥有自己独立的数据。
class Dog: # __init__ 是构造方法,创建对象时自动调用 def __init__(self, name, age): self.name = name # 实例属性:每只狗有自己的名字 self.age = age def bark(self): # 实例方法:所有狗都会叫 return f"{self.name} says: Woof!"# 创建两个独立的对象buddy = Dog("Buddy", 3)max_dog = Dog("Max", 5)print(buddy.bark()) # Buddy says: Woof!print(max_dog.bark()) # Max says: Woof!
💡 核心概念:self 代表当前对象本身。在方法内部通过 self.xxx访问该对象自己的属性和其他方法。它是区分“这个对象”和“那个对象”的关键。二、封装:隐藏细节,暴露接口
封装不是“藏起来不让看”,而是明确边界:哪些是外部可以安全使用的接口,哪些是内部实现细节不应被随意改动。
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self._balance = balance # 单下划线:约定为内部使用,请勿直接访问 def deposit(self, amount): if amount <= 0: raise ValueError("存款金额必须大于0") self._balance += amount def get_balance(self): return self._balance
_balance 前的单下划线是一种约定,告诉其他开发者“这是内部状态,请通过 deposit() 和 get_balance() 操作”。- 如果未来要加日志、校验、事务,只需改方法内部,调用方代码完全不用动。这就是封装的价值:变化被隔离在内部。
⚠️ 注意:Python 没有真正的私有机制(双下划线 __ 只是名称改写,仍可绕过)。不要迷信“强制私有”,良好的设计和文档比语法限制更重要。
三、继承:复用与扩展的利器
当多个类有共同特征时,提取父类避免重复;子类继承父类后,可添加新功能或重写已有行为。
class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError("子类必须实现 speak()")class Cat(Animal): def speak(self): return f"{self.name}: Meow"class Duck(Animal): def speak(self): return f"{self.name}: Quack"# 多态:同一接口,不同表现animals = [Cat("Whiskers"), Duck("Donald")]for animal in animals: print(animal.speak()) # 自动调用各自重写的版本
- 复用:
name 属性和初始化逻辑只在 Animal 中写一次。 - 扩展:新增动物类型只需新建子类,无需修改已有代码。
- 多态:调用方只关心“会说话”,不关心具体是什么动物。这让代码对扩展开放、对修改关闭。
🎯 黄金原则:优先用组合而非继承。只有当“A 是 B 的一种”时才用继承;如果是“A 有 B”的关系(如汽车有引擎),应该把引擎作为属性传入,而不是让汽车继承引擎。
四、实战:任务管理系统(OOP 版)
用类重构之前的待办事项工具,体会 OOP 如何提升结构清晰度:
import jsonfrom pathlib import Pathclass TodoItem: """单个待办项""" def __init__(self, text, done=False): self.text = text self.done = done def to_dict(self): return {"text": self.text, "done": self.done} @classmethod def from_dict(cls, data): return cls(data["text"], data["done"])class TodoList: """待办列表管理器""" def __init__(self, filepath=".todo.json"): self.filepath = Path(filepath) self.items = [] self._load() def _load(self): if self.filepath.exists(): data = json.loads(self.filepath.read_text(encoding="utf-8")) self.items = [TodoItem.from_dict(d) for d in data] def save(self): data = [item.to_dict() for item in self.items] self.filepath.write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8" ) def add(self, text): self.items.append(TodoItem(text)) self.save() def mark_done(self, index): if 0 <= index < len(self.items): self.items[index].done = True self.save()
对比第九课的函数式版本,OOP 版的优势一目了然:
- 职责分离:
TodoItem只管自身数据转换,TodoList 只管集合操作和持久化。 - 内聚性高:加载/保存逻辑紧贴数据,不会散落到全局函数里。
- 易于测试:可以单独测试
TodoItem.from_dict() 是否正确,无需启动整个程序。 - 可扩展:想加优先级、截止日期?只需扩展
TodoItem
五、新手避坑指南
- 过度设计:三个函数能解决的问题不要硬套 OOP。OOP 是为复杂度服务的,不是炫技工具。
- 滥用继承层级:超过三层继承就该警惕。深层次的继承链会让代码难以理解和调试。
- 忽略 self:忘记在方法参数中写
self,或在方法体内漏掉 self. 前缀,是最常见的低级错误。 - 把类当命名空间用:如果一个类只有静态方法、没有实例状态,它可能只是一个模块,不该写成类。
- 混淆类属性和实例属性:
class Foo: x = [] 中的x是所有实例共享的!可变对象作类属性极易引发 bug,务必在__init__中初始化实例属性。
六、总结
- 类与对象:蓝图与实例,
__init__ 初始化,self 指向自身。 - 封装:用约定保护内部状态,通过方法暴露稳定接口。
- 继承与多态:复用共性,扩展个性,同一接口不同实现。
- 设计优先:OOP 是手段不是目的,简单问题用简单方案,复杂问题才上对象建模。
至此,Python 基础语法十课圆满收官!你已经完成了从变量、控制流、数据结构、文件处理、模块生态到面向对象的全链路学习。接下来,你可以选择深入 Web 开发、数据分析、自动化运维或 AI 等方向,但无论哪条路,这十课打下的地基都将是你最坚实的支撑。