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 += 1
print(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 familiaris
dog1.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 = owner self.__balance = initial_balance # 私有属性 def deposit(self, amount): if amount > 0: self.__balance += amount print(f"存款 {amount} 元成功。") else: print("存款金额必须大于0。") def withdraw(self, amount): if 0 < amount <= self.__balance: self.__balance -= amount print(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 = name
self.species = species
def 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 = age
self.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 = age
self.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) # 先让动物叫一声
# 尝试调用特定行为,但需要检查类型或使用 hasattr
if 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:
@staticmethod
def add(x, y):
"""一个与MathUtils类相关的加法工具函数"""
return x + y
@staticmethod
def 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 = name
self.age = age
@classmethod
def 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类添加一个静态方法(如计算油耗)和一个类方法(如从配置文件创建实例)。
925

被折叠的 条评论
为什么被折叠?



