介绍
在这个实验中,你将对 Python 中的关键面向对象编程 (OOP) 概念获得实践性的理解。我们将从封装 (encapsulation) 开始,学习如何将数据和方法捆绑在类中,并通过私有属性 (private attributes) 来控制对数据的访问。
接下来,你将实现继承 (inheritance) 来构建类之间的关系,这有助于代码重用。我们还将探索多态性 (polymorphism),它允许不同类的对象被统一对待。最后,你将使用 super() 方法来有效地调用父类 (parent class) 的方法,并练习多重继承 (multiple inheritance),以了解一个类如何可以从多个父类继承。
使用基础类探索封装
在这一步,我们将探索 **封装 (encapsulation)**,这是一个核心的 OOP 原则。封装涉及将数据(属性)和操作该数据的方法捆绑到一个单一单元——类中。它还限制了对对象内部状态的直接访问,这有助于防止数据被意外修改。
在 Python 中,我们使用命名约定来表示一个属性是“私有的”。在属性前加上一个下划线(例如 _name)表示它仅供内部使用。虽然这并非严格强制执行,但它是一个开发者会遵守的强约定。
我们将通过创建两个独立的类 Dog 和 Cat 开始,以观察它们的结构方式。
首先,在 WebIDE 左侧的文件浏览器中找到文件 animal_classes.py。打开它并添加以下 Python 代码。此代码定义了 Dog 类和 Cat 类,每个类都有一个私有 _name 属性以及与之交互的方法。
## File: animal_classes.py
class Dog:
def __init__(self, name):
## 单一下划线前缀表示一个“私有”属性。
self._name = name
## 用于获取私有属性值的公共方法。
def get_name(self):
return self._name
## 用于设置私有属性值的公共方法。
def set_name(self, value):
self._name = value
def say(self):
print(f"{self._name} says: Woof!")
class Cat:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
def say(self):
print(f"{self._name} says: Meow!")
## 只有当脚本被直接执行时,此代码块才会运行。
if __name__ == "__main__":
## 创建 Dog 类的一个实例
my_dog = Dog("Buddy")
print(f"Initial dog name: {my_dog.get_name()}")
## 使用 setter 方法更改狗的名字
my_dog.set_name("Rocky")
print(f"New dog name: {my_dog.get_name()}")
my_dog.say()
print("-" * 20)
## 创建 Cat 类的一个实例
my_cat = Cat("Whiskers")
print(f"Cat name: {my_cat.get_name()}")
my_cat.say()
添加代码后,保存文件。
现在,让我们运行脚本以查看封装的实际效果。在 WebIDE 中打开终端并执行以下命令:
python animal_classes.py
你将看到以下输出,它表明我们正在通过公共的 get_name 和 set_name 方法与私有的 _name 属性进行交互。
Initial dog name: Buddy
New dog name: Rocky
Rocky says: Woof!
--------------------
Cat name: Whiskers
Whiskers says: Meow!
<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="%E5%AE%9E%E7%8E%B0%E7%BB%A7%E6%89%BF%E5%92%8C%E5%A4%9A%E6%80%81"></div>
## 实现继承和多态
在上一个步骤中,你可能已经注意到 `Dog` 和 `Cat` 类共享了许多相同的代码(`__init__`、`get_name`、`set_name`)。这是一个使用 **继承 (inheritance)** 的绝佳机会。继承允许一个新类(子类或 subclass)从一个现有类(父类或 superclass)继承属性和方法,从而促进代码重用。
我们还将介绍 **多态性 (polymorphism)**,它的意思是“多种形式”。在 OOP 中,它指的是不同类能够以各自独特的方式响应相同的(方法)调用。
让我们重构代码。我们将创建一个父类 `Animal` 来容纳通用代码,并让 `Dog` 和 `Cat` 从它那里继承。`say` 方法在每个类中都不同,它将演示多态性。
打开 `animal_classes.py` 文件,并用以下代码替换其全部内容:
```python
## File: animal_classes.py
class Animal:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
def say(self):
print(f"{self._name} makes a generic animal sound.")
## Dog 继承自 Animal
class Dog(Animal):
## 这会覆盖 Animal 类中的 say() 方法
def say(self):
print(f"{self._name} says: Woof!")
## Cat 继承自 Animal
class Cat(Animal):
## 这也会覆盖 say() 方法
def say(self):
print(f"{self._name} says: Meow!")
def make_animal_speak(animal_instance):
animal_instance.say()
if __name__ == "__main__":
generic_animal = Animal("Creature")
my_dog = Dog("Buddy")
my_cat = Cat("Whiskers")
print("--- Calling say() on each object ---")
generic_animal.say()
my_dog.say()
my_cat.say()
print("\n--- Demonstrating Polymorphism ---")
make_animal_speak(generic_animal)
make_animal_speak(my_dog)
make_animal_speak(my_cat)
保存文件。注意 Dog 和 Cat 类现在是如何变得更简洁的。它们从 Animal 继承了 __init__、get_name 和 set_name 方法。它们各自提供了自己的 say 方法版本,这是方法重写 (method overriding) 的一个例子。
现在,从终端运行更新后的脚本:
python animal_classes.py
输出将是:
--- Calling say() on each object ---
Creature makes a generic animal sound.
Buddy says: Woof!
Whiskers says: Meow!
--- Demonstrating Polymorphism ---
Creature makes a generic animal sound.
Buddy says: Woof!
Whiskers says: Meow!
make_animal_speak 函数接收任何具有 say 方法的对象。即使我们向它传递不同类型的对象(Animal、Dog、Cat),它也能正常工作,因为每个对象都知道如何以自己的方式执行 say 操作。这就是多态性的威力。
使用 super() 方法扩展功能
当子类覆盖了其父类的方法时,它有时需要扩展父类的方法,而不仅仅是替换它。super() 函数提供了一种在子类内部调用父类方法的方式。
这在 __init__ 方法中非常常见。子类通常需要在执行完父类的初始化步骤后,再执行自己特有的初始化步骤。
让我们向 Dog 和 Cat 类添加唯一的属性。Dog 将有一个 age(年龄),Cat 将有一个 color(颜色)。我们将使用 super() 来确保父类 Animal 的 __init__ 方法仍然被调用,以设置 _name 属性。
修改 animal_classes.py 文件,用以下代码替换其内容:
## File: animal_classes.py
class Animal:
def __init__(self, name):
print(f"Animal __init__ called for {name}")
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
self._name = value
def say(self):
print(f"{self._name} makes a generic animal sound.")
class Dog(Animal):
def __init__(self, name, age):
## 调用父类的 __init__ 方法来处理 'name' 属性
super().__init__(name)
print("Dog __init__ called")
self.age = age
def say(self):
## 我们也可以使用 super() 来调用父类的 say() 方法
## super().say()
print(f"{self._name} says: Woof! I am {self.age} years old.")
class Cat(Animal):
def __init__(self, name, color):
## 调用父类的 __init__ 方法
super().__init__(name)
print("Cat __init__ called")
self.color = color
def say(self):
print(f"{self._name} says: Meow! I have {self.color} fur.")
if __name__ == "__main__":
my_dog = Dog("Buddy", 5)
my_dog.say()
print("-" * 20)
my_cat = Cat("Whiskers", "black")
my_cat.say()
保存文件。在这个版本中,Dog.__init__ 和 Cat.__init__ 首先调用 super().__init__(name)。这会执行 Animal.__init__ 中的代码,从而设置 _name 属性。之后,它们继续执行自己特有的初始化(self.age = age 和 self.color = color)。
从终端运行脚本:
python animal_classes.py
输出展示了 __init__ 调用的链式过程以及扩展后的 say 方法:
Animal __init__ called for Buddy
Dog __init__ called
Buddy says: Woof! I am 5 years old.
--------------------
Animal __init__ called for Whiskers
Cat __init__ called
Whiskers says: Meow! I have black fur.
<div class="pt-24 -mt-20 relative -z-10 anchor-item" id="%E7%BB%83%E4%B9%A0%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF"></div>
## 练习多重继承
Python 允许一个类从多个父类继承。这被称为**多重继承 (multiple inheritance)**。它可以成为混合来自不同来源的功能的强大工具,但它也带来了复杂性,特别是在它们具有相同名称时,Python 如何决定使用哪个父类的方法。
这种搜索顺序被称为**方法解析顺序 (Method Resolution Order, MRO)**。Python 使用一种称为 C3 线性化 (C3 linearization) 的算法来确定一致且可预测的 MRO。
让我们用一个新的例子来探索这一点。从文件浏览器中打开 `multiple_inheritance.py` 文件,并添加以下代码:
```python
## File: multiple_inheritance.py
class ParentA:
def speak(self):
print("Speaking from ParentA")
def common_method(self):
print("ParentA's common method")
class ParentB:
def speak(self):
print("Speaking from ParentB")
def common_method(self):
print("ParentB's common method")
## Child 继承自 A,然后是 B
class Child_AB(ParentA, ParentB):
pass
## Child 继承自 B,然后是 A
class Child_BA(ParentB, ParentA):
def common_method(self):
print("Child_BA's own common method")
if __name__ == "__main__":
child1 = Child_AB()
child2 = Child_BA()
print("--- Investigating Child_AB (ParentA, ParentB) ---")
child1.speak()
child1.common_method()
## .mro() 方法显示了方法解析顺序
print("MRO for Child_AB:", [c.__name__ for c in Child_AB.mro()])
print("\n--- Investigating Child_BA (ParentB, ParentA) ---")
child2.speak()
child2.common_method()
print("MRO for Child_BA:", [c.__name__ for c in Child_BA.mro()])
保存文件。在这里,Child_AB 继承自 ParentA,然后是 ParentB。Child_BA 则以相反的顺序继承。当调用一个方法时,Python 会按照 MRO 指定的顺序进行查找。
从终端运行脚本:
python multiple_inheritance.py
你将看到以下输出:
--- Investigating Child_AB (ParentA, ParentB) ---
Speaking from ParentA
ParentA's common method
MRO for Child_AB: ['Child_AB', 'ParentA', 'ParentB', 'object']
--- Investigating Child_BA (ParentB, ParentA) ---
Speaking from ParentB
Child_BA's own common method
MRO for Child_BA: ['Child_BA', 'ParentB', 'ParentA', 'object']
从输出中,你可以观察到:
child1.speak()调用了ParentA中的方法,因为ParentA在Child_AB的 MRO 中排在前面。child2.speak()调用了ParentB中的方法,因为ParentB在Child_BA的 MRO 中排在前面。child2.common_method()调用了在Child_BA中直接定义的那一个版本,因为 Python 首先在那里找到了它,然后再检查父类。
理解 MRO 对于预测多重继承场景下的行为至关重要。
总结
在这个实验中,你亲手实践了 Python 中面向对象编程的四个基本概念。
你从封装 (encapsulation) 开始,学习了通过约定俗成的方式使用私有属性来保护类数据,并提供公共方法进行访问。然后,你重构了代码以使用**继承 (inheritance)**,创建了一个父类 Animal,以减少 Dog 和 Cat 子类中的代码重复。
在实现继承的过程中,你看到了多态 (polymorphism) 的实际应用,因为 Dog 和 Cat 对象对相同的 say() 方法调用做出了不同的响应。你学会了使用 super() 方法来调用和扩展父类的功能,尤其是在 __init__ 方法内部。最后,你探索了多重继承 (multiple inheritance) 以及方法解析顺序 (Method Resolution Order, MRO) 在决定调用哪个父类方法中的重要性。



