Python入门第9课:面向对象编程(OOP)从零开始,类、对象与三大特性
Python入门第9课:面向对象编程(OOP)从零开始,类、对象与三大特性
作者: 蛋皮
标签: Python, 面向对象编程, OOP, 类, 对象, 封装, 继承, 多态, 编程基础
欢迎回到Python入门系列!在上一课中,我们学习了如何通过模块与包来组织代码,让程序结构更清晰。今天,我们将进入一个更高级、更强大的编程范式——面向对象编程 (Object-Oriented Programming, OOP)。这是现代软件开发的基石之一,掌握它将彻底改变你设计和构建程序的方式。
想象一下,你不是在编写一系列零散的函数和变量,而是在创造一个个能够“思考”和“行动”的智能实体。这些实体拥有自己的数据和行为,可以相互协作。这就是面向对象编程的魅力!
什么是面向对象编程 (OOP)?
传统的过程式编程(我们之前主要使用的)是将程序视为一系列按顺序执行的步骤(函数)。而面向对象编程则是将程序视为一组对象 (Objects) 的集合。每个对象都是现实世界中某个事物的软件模型。
- 对象 (Object): 程序中的一个具体实例,比如一只“猫”、一辆“汽车”、一个“用户账户”。对象拥有状态 (State) 和 行为 (Behavior)。
- 类 (Class): 是创建对象的蓝图 (Blueprint) 或 模板 (Template)。它定义了对象应该具有哪些状态(属性)和行为(方法)。
核心思想: 万物皆对象。我们通过定义类来描述一类事物的共同特征,然后根据类创建具体的对象来使用。
核心概念:类 (Class) 与 对象 (Object)
1. 定义一个类
使用 class
关键字来定义一个类。类名通常采用大驼峰命名法 (PascalCase)。
class Dog:"""这是一个Dog类,代表一只狗。"""# 类属性 (Class Attribute): 所有Dog实例共享的属性species = "Canis lupus familiaris"# 构造方法 (Constructor): 在创建对象时自动调用def __init__(self, name, age):# 实例属性 (Instance Attribute): 每个对象独有的属性self.name = name # self 指向当前创建的对象实例self.age = age# 实例方法 (Instance Method): 对象可以执行的行为def bark(self):print(f"{self.name} says: Woof! Woof!")def get_info(self):return f"名字: {self.name}, 年龄: {self.age}岁, 物种: {Dog.species}"# 另一个实例方法def have_birthday(self):self.age += 1print(f"祝 {self.name} 生日快乐!现在 {self.name} {self.age} 岁了!")
代码解析:
class Dog:
: 定义一个名为Dog
的类。species = "..."
: 类属性。这个属性属于Dog
类本身,所有Dog
的实例都共享这个值。def __init__(self, name, age):
: 构造方法。名字固定为__init__
。当创建Dog
的新实例时,这个方法会被自动调用。self
是一个特殊的参数,它代表即将被创建的这个具体对象。name
和age
是我们希望为每个狗实例设置的参数。self.name = name
: 在构造方法中,使用self.属性名 = 值
的方式为当前对象(self
)设置实例属性。每个Dog
对象都有自己的name
和age
。def bark(self):
: 实例方法。方法的第一个参数必须是self
,这样方法内部才能访问该对象的属性和其他方法。bark
方法定义了狗可以“汪汪叫”的行为。
2. 创建对象 (实例化)
使用类名加上括号 ()
来创建对象,这个过程称为实例化 (Instantiation)。
# 创建Dog类的两个实例 (对象)
dog1 = Dog("旺财", 3)
dog2 = Dog("小黑", 5)# 访问对象的属性
print(dog1.name) # 输出: 旺财
print(dog2.age) # 输出: 5# 访问类属性 (可以通过类名或实例访问)
print(Dog.species) # 推荐方式: 通过类名访问
print(dog1.species) # 也可以通过实例访问# 调用对象的方法
dog1.bark() # 输出: 旺财 says: Woof! Woof!
dog2.bark() # 输出: 小黑 says: Woof! Woof!# 调用其他方法
info1 = dog1.get_info()
print(info1) # 输出: 名字: 旺财, 年龄: 3岁, 物种: Canis lupus familiarisdog1.have_birthday() # 输出: 祝 旺财 生日快乐!现在 旺财 4 岁了!
print(dog1.get_info()) # 再次获取信息,年龄已更新
关键点:
dog1
和dog2
是Dog
类的两个不同的实例。- 它们有各自的
name
和age
(实例属性)。 - 它们共享
species
(类属性)。 - 它们都可以调用
bark()
,get_info()
,have_birthday()
等方法。
OOP的三大特性
面向对象编程有三大核心特性:封装 (Encapsulation)、继承 (Inheritance) 和 多态 (Polymorphism)。它们是OOP强大和灵活的根源。
1. 封装 (Encapsulation)
定义: 将对象的数据(属性) 和 操作数据的代码(方法) 集成在一个单元(即类)中,并尽可能隐藏对象的内部实现细节。外部代码通过对象提供的接口(方法) 来与对象交互,而不是直接操作其内部数据。
目的: 提高代码的安全性、可维护性和复用性。隐藏复杂性,暴露简单接口。
在Python中如何实现封装?
Python没有像Java那样的 private
关键字,但它通过命名约定来实现封装:
- 公有 (Public): 属性和方法名没有下划线前缀。可以从类外部自由访问。
class MyClass:def __init__(self):self.public_attr = "我是公开的" # 公有属性def public_method(self): # 公有方法return "我是公开的方法"
- 受保护 (Protected): 使用单个下划线
_
作为前缀(如_attr
,_method
)。这是一种约定,表示该属性或方法是“内部使用”的,不建议在类外部直接访问,但Python不会阻止你。class MyClass:def __init__(self):self._internal_data = "内部数据,不建议直接访问"def _helper_function(self): # 辅助函数return "内部辅助"
- 私有 (Private): 使用双下划线
__
作为前缀(如__attr
,__method
)。Python会对其进行名称改写 (Name Mangling),使其在类外部更难访问(但不是绝对不可能),强制通过类的方法来操作。class BankAccount:def __init__(self, owner, initial_balance):self.owner = ownerself.__balance = initial_balance # 私有属性def deposit(self, amount):if amount > 0:self.__balance += amountprint(f"存款 {amount} 元成功。")else:print("存款金额必须大于0。")def withdraw(self, amount):if 0 < amount <= self.__balance:self.__balance -= amountprint(f"取款 {amount} 元成功。")else:print("余额不足或取款金额无效。")def get_balance(self): # 公有方法,用于安全地获取余额return self.__balance# 使用 account = BankAccount("张三", 1000) account.deposit(500) # 正确方式 account.withdraw(200) # 正确方式 print(f"当前余额: {account.get_balance()}") # 通过公有方法获取# 尝试直接访问私有属性 (不推荐,且会报错) # print(account.__balance) # AttributeError! # 实际上,Python将其改写为 _BankAccount__balance # print(account._BankAccount__balance) # 可以访问,但破坏了封装原则,不要这样做!
封装的好处: 在 BankAccount
例子中,我们通过 deposit
和 withdraw
方法控制了对 __balance
的修改,确保了业务逻辑(如不能取负数、不能透支)被强制执行,保护了数据的完整性。
2. 继承 (Inheritance)
定义: 一个类(子类/派生类)可以继承另一个类(父类/基类)的属性和方法。子类在拥有父类所有功能的基础上,可以扩展新的属性和方法,或者重写 (Override) 父类的方法以实现不同的行为。
目的: 实现代码复用,建立类之间的层次关系,体现“is-a”关系(如“狗”是“动物”的一种)。
语法: class 子类名(父类名):
# 定义一个更通用的父类
class Animal:def __init__(self, name, species):self.name = nameself.species = speciesdef make_sound(self):print(f"{self.name} 发出了一种声音。")def sleep(self):print(f"{self.name} 正在睡觉。")# Dog类继承Animal类
class Dog(Animal):def __init__(self, name, age, breed="未知品种"):# 调用父类的构造方法,初始化从父类继承来的属性super().__init__(name, "Canis lupus familiaris")# Dog类特有的属性self.age = ageself.breed = breed# 重写 (Override) 父类的 make_sound 方法def make_sound(self):print(f"{self.name} 汪汪叫: Woof! Woof!")# 扩展:Dog类特有的方法def fetch(self):print(f"{self.name} 正在捡球!")# Cat类也继承Animal类
class Cat(Animal):def __init__(self, name, age, color="未知颜色"):super().__init__(name, "Felis catus")self.age = ageself.color = color# 重写 make_sound 方法def make_sound(self):print(f"{self.name} 喵喵叫: Meow!")# Cat类特有的方法def climb_tree(self):print(f"{self.name} 正在爬树!")# 使用继承
dog = Dog("旺财", 3, "金毛")
cat = Cat("咪咪", 2, "橘色")# 调用继承的方法
dog.sleep() # 调用父类Animal的sleep方法
cat.sleep()# 调用重写的方法
dog.make_sound() # 输出: 旺财 汪汪叫: Woof! Woof!
cat.make_sound() # 输出: 咪咪 喵喵叫: Meow!# 调用子类特有的方法
dog.fetch() # 输出: 旺财 正在捡球!
cat.climb_tree() # 输出: 咪咪 正在爬树!# 访问继承的和特有的属性
print(f"{dog.name} 是 {dog.breed} 品种。")
print(f"{cat.name} 是 {cat.color} 色的。")
关键点:
super().__init__(...)
: 这是调用父类构造方法的标准方式,确保父类的初始化代码被执行。- 方法重写 (Method Overriding): 子类定义一个与父类同名的方法,当调用该方法时,子类的方法会“覆盖”父类的方法。
- 方法扩展: 子类可以添加父类没有的新方法。
3. 多态 (Polymorphism)
定义: 多态字面意思是“多种形态”。在OOP中,它指的是同一个接口(方法名)在不同的对象上可以有不同的实现方式。多态通常与继承和方法重写一起使用。
目的: 提高代码的灵活性和可扩展性。允许我们编写能处理多种类型对象的通用代码。
核心思想: “一个接口,多种实现”。
# 继续使用上面的 Animal, Dog, Cat 类# 定义一个函数,它接受任何 Animal 类型的对象
def animal_sound(animal):"""这个函数不关心传入的是哪种具体的动物,它只知道所有动物都有 make_sound 方法。"""animal.make_sound() # 调用 make_sound 方法# 创建不同类型的动物对象
animals = [Dog("旺财", 3), Cat("咪咪", 2), Animal("未知生物", "未知")]# 对列表中的每个动物调用 animal_sound 函数
for animal in animals:animal_sound(animal)# 输出:# 旺财 汪汪叫: Woof! Woof!# 咪咪 喵喵叫: Meow!# 未知生物 发出了一种声音。print("---")# 另一个例子:一个能和动物玩耍的函数
def play_with_animal(animal):print(f"正在和 {animal.name} 玩耍...")animal_sound(animal) # 先让动物叫一声# 尝试调用特定行为,但需要检查类型或使用 hasattrif isinstance(animal, Dog):animal.fetch()elif isinstance(animal, Cat):animal.climb_tree()else:print(f"{animal.name} 不知道玩什么。")# 测试
for animal in animals:play_with_animal(animal)print()
多态的体现:
animal_sound
函数的参数animal
可以是Animal
、Dog
或Cat
类型的任何对象。- 当我们调用
animal.make_sound()
时,Python会根据animal
实际指向的对象类型,自动调用相应类中定义的make_sound
方法。 - 这就是运行时多态 (Runtime Polymorphism) 或 动态绑定 (Dynamic Binding)。代码在运行时才决定具体调用哪个方法。
多态的好处: 我们的 animal_sound
函数非常通用。如果以后我们创建了 Bird
类并让它继承 Animal
且重写 make_sound
方法,这个函数无需修改就能正确处理 Bird
对象!这极大地增强了代码的可扩展性。
静态方法与类方法
除了实例方法,Python类还支持两种特殊的方法:
1. 静态方法 (@staticmethod
)
- 不需要
self
或cls
参数。 - 它本质上是一个属于类的普通函数,不会访问类或实例的任何数据。
- 通常用于与类相关的工具函数。
- 用
@staticmethod
装饰器标记。
class MathUtils:@staticmethoddef add(x, y):"""一个与MathUtils类相关的加法工具函数"""return x + y@staticmethoddef is_even(number):return number % 2 == 0# 调用静态方法:可以直接通过类名调用,不需要创建实例
result = MathUtils.add(5, 3)
print(result) # 8
print(MathUtils.is_even(4)) # True
2. 类方法 (@classmethod
)
- 第一个参数是
cls
,代表类本身,而不是实例。 - 可以访问类属性,但不能访问实例属性(因为没有
self
)。 - 常用于创建替代构造函数 (Alternative Constructors)。
- 用
@classmethod
装饰器标记。
class Person:species = "Homo sapiens"def __init__(self, name, age):self.name = nameself.age = age@classmethoddef from_string(cls, person_str):"""一个替代构造函数,可以从 "姓名-年龄" 格式的字符串创建Person实例。cls 指向 Person 类。"""name, age_str = person_str.split('-')return cls(name, int(age_str)) # 等同于 Person(name, int(age_str))def introduce(self):return f"大家好,我是 {self.name},今年 {self.age} 岁,属于 {Person.species}。"# 使用替代构造函数
person1 = Person("Alice", 25)
person2 = Person.from_string("Bob-30") # 无需手动分割字符串print(person1.introduce())
print(person2.introduce())
OOP最佳实践与总结
- 何时使用OOP? 当你的程序涉及多个具有相似属性和行为的实体,或者逻辑变得复杂时,OOP是很好的选择。简单的脚本可能不需要。
- 高内聚,低耦合: 类内部的属性和方法应该紧密相关(高内聚)。类与类之间的依赖应该尽量少(低耦合)。
- 单一职责原则 (SRP): 一个类应该只有一个引起它变化的原因。即,一个类只负责一项职责。
- 善用继承: 继承是强大的,但不要滥用。优先考虑“组合 (Composition)”而非“继承 (Inheritance)”。即,一个类包含另一个类的对象,而不是继承它。
- 清晰的命名: 类名、方法名、属性名要清晰、有意义。
- 编写文档: 使用文档字符串 (
"""..."""
) 为类和方法编写说明。
总结
今天我们深入学习了面向对象编程的核心概念:
- 类 (Class) 是蓝图,对象 (Object) 是根据蓝图创建的实例。
- 封装 隐藏内部细节,通过接口与外界交互,保护数据安全。
- 继承 实现代码复用,建立类的层次结构。
- 多态 允许同一接口在不同对象上有不同实现,提高代码灵活性。
OOP是一种强大的思维方式,它帮助我们将复杂的世界映射到代码中,使程序更易于理解、维护和扩展。虽然一开始可能需要一些适应,但一旦掌握,你将能构建出更加优雅和健壮的软件。
练习建议:
- 创建一个
Car
类,包含品牌、型号、年份等属性,以及启动、加速、刹车等方法。 - 创建一个
Vehicle
父类,让Car
和Motorcycle
继承它。 - 创建一个
BankAccount
类,包含存款、取款、查询余额等方法,并使用私有属性保护余额。 - 尝试为你的
Car
类添加一个静态方法(如计算油耗)和一个类方法(如从配置文件创建实例)。