简介
面向对象编程创建了可重复使用的代码模式,以减少开发项目中的冗余。面向对象编程实现可重复使用代码的一种方式是通过继承,即一个子类可以利用另一个基类的代码。
本教程将介绍 Python 中继承的一些主要方面,包括父类和子类的工作原理,如何覆盖方法和属性,如何使用 super()
函数以及如何使用多重继承。
先决条件
您应该已经安装了 Python 3,并在计算机或服务器上设置了编程环境。如果您还没有设置编程环境,可以参考适用于您操作系统(Ubuntu、CentOS、Debian 等)的本地编程环境或服务器编程环境的安装和设置指南。
什么是继承?
继承 是指一个类使用在另一个类中构建的代码。如果我们从生物学的角度来看待继承,我们可以将子类从父类那里继承某些特征。也就是说,子类可以继承父类的身高或眼睛颜色。子类也可能与父类共享相同的姓氏。
称为子类或派生类的类从父类或基类中继承方法和变量。
我们可以想象一个名为 Parent
的父类,它具有 last_name
、height
和 eye_color
的类变量,子类 Child
将从 Parent
继承这些变量。
由于 Child
子类是从 Parent
基类继承的,因此 Child
类可以重用 Parent
的代码,使程序员能够使用更少的代码行并减少冗余。
父类
父类或基类创建了一个模式,可以基于该模式创建子类或派生类。父类允许我们通过继承创建子类,而无需每次都重复编写相同的代码。任何类都可以成为父类,因此它们都是各自完全功能的类,而不仅仅是模板。
假设我们有一个名为 Bank_account
的通用父类,它有 Personal_account
和 Business_account
子类。个人账户和企业账户之间的许多方法将是相似的,例如提取和存款资金的方法,因此这些方法可以属于 Bank_account
的父类。Business_account
子类将具有特定于其自身的方法,包括可能的一种方式来收集业务记录和表格,以及一个 employee_identification_number
变量。
类似地,Animal
类可能具有 eating()
和 sleeping()
方法,而 Snake
子类可能包括其自己特定的 hissing()
和 slithering()
方法。
让我们创建一个名为 Fish
的父类,稍后我们将使用它来构建其子类。每种鱼都将具有名和姓以及特征。
我们将创建一个名为 fish.py
的新文件,并从 __init__()
构造方法开始,我们将为每个 Fish
对象或子类填充 first_name
和 last_name
类变量。
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
我们使用字符串 "Fish"
初始化了我们的 last_name
变量,因为我们知道大多数鱼都会将其作为姓氏。
让我们再添加一些其他方法:
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
<^>def swim(self):<^>
<^>print("The fish is swimming.")<^>
<^>def swim_backwards(self):<^>
<^>print("The fish can swim backwards.")<^>
我们向 Fish
类添加了 swim()
和 swim_backwards()
方法,以便每个子类也能够使用这些方法。
由于我们将要创建的大多数鱼被认为是骨鱼(它们的骨架由骨头构成),而不是软骨鱼(它们的骨架由软骨构成),我们可以在 __init__()
方法中添加一些其他属性:
class Fish:
def __init__(self, first_name, last_name="Fish",
<^>skeleton="bone", eyelids=False<^>):
self.first_name = first_name
self.last_name = last_name
<^>self.skeleton = skeleton<^>
<^>self.eyelids = eyelids<^>
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")
构建父类遵循与构建任何其他类相同的方法,只是我们要考虑一旦创建了子类,子类将能够使用哪些方法。
子类
子类或派生类是将从父类继承的类。这意味着每个子类都可以使用父类的方法和变量。
例如,一个名为 Goldfish
的子类,它是 Fish
类的子类,将能够使用 Fish
中声明的 swim()
方法,而无需声明它。
我们可以将每个子类视为父类的一种类。也就是说,如果我们有一个名为 Rhombus
的子类和一个名为 Parallelogram
的父类,我们可以说 Rhombus
是一个 Parallelogram
,就像 Goldfish
是一个 Fish
一样。
子类的第一行看起来与非子类的类有些不同,因为您必须将父类作为参数传递给子类:
class Trout(Fish):
Trout
类是 Fish
类的子类。我们知道这一点是因为括号中包含了 Fish
这个词。
对于子类,我们可以选择添加更多方法、覆盖现有的父类方法,或者使用 pass
关键字接受默认的父类方法,这在本例中我们将这样做:
...
class Trout(Fish):
pass
现在,我们可以创建一个 Trout
对象,而无需定义任何额外的方法。
...
class Trout(Fish):
pass
terry = Trout("Terry")
print(terry.first_name + " " + terry.last_name)
print(terry.skeleton)
print(terry.eyelids)
terry.swim()
terry.swim_backwards()
我们创建了一个 Trout
对象 terry
,它使用了 Fish
类的每个方法,即使我们没有在 Trout
子类中定义这些方法。我们只需要将值 "Terry"
传递给 first_name
变量,因为所有其他变量都已经初始化。
当我们运行程序时,我们将收到以下输出:
Terry Fish
bone
False
The fish is swimming.
The fish can swim backwards.
接下来,让我们创建另一个包含自己方法的子类。我们将称这个类为 Clownfish
,它的特殊方法将允许它与海葵共存:
...
class Clownfish(Fish):
def live_with_anemone(self):
print("The clownfish is coexisting with sea anemone.")
接下来,让我们创建一个 Clownfish
对象,看看它是如何工作的:
...
casey = Clownfish("Casey")
print(casey.first_name + " " + casey.last_name)
casey.swim()
casey.live_with_anemone()
当我们运行程序时,我们将收到以下输出:
Casey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.
输出显示,Clownfish
对象 casey
能够使用 Fish
的 __init__()
和 swim()
方法,以及它自己的子类方法 live_with_anemone()
。
如果我们尝试在 Trout
对象中使用 live_with_anemone()
方法,我们将收到一个错误:
terry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'
这是因为 live_with_anemone()
方法仅属于 Clownfish
子类,而不属于 Fish
父类。
子类继承了它所属的父类的方法,因此每个子类都可以在程序中使用这些方法。
覆盖父类方法
到目前为止,我们已经看到了子类 Trout
使用 pass
关键字继承了父类 Fish
的所有行为,以及另一个子类 Clownfish
继承了父类的所有行为,并且还创建了自己独特的方法,该方法特定于子类。然而,有时我们希望使用父类的一些行为,但不是全部。当我们更改父类方法时,我们会覆盖它们。
在构建父类和子类时,重要的是要牢记程序设计,以便覆盖不会产生不必要或冗余的代码。
我们将创建一个 Shark
子类,它是 Fish
父类的子类。因为我们创建了 Fish
类时,我们的主要目的是创建主要是骨鱼,所以我们需要对 Shark
类进行调整,因为它是软骨鱼。在程序设计方面,如果我们有不止一种非骨鱼,我们很可能希望为这两种鱼类创建单独的类。
与骨鱼不同,鲨鱼的骨架由软骨而不是骨头构成。它们也有眼睑,并且无法向后游泳。然而,鲨鱼可以通过下沉来向后移动。
鉴于这一点,我们将覆盖 __init__()
构造方法和 swim_backwards()
方法。我们不需要修改 swim()
方法,因为鲨鱼是可以游泳的鱼类。让我们来看看这个子类:
...
class Shark(Fish):
def __init__(self, first_name, last_name="Shark",
skeleton="cartilage", eyelids=True):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim_backwards(self):
print("The shark cannot swim backwards, but can sink backwards.")
我们已经覆盖了 __init__()
方法中的初始化参数,因此现在 last_name
变量设置为字符串 "Shark"
,skeleton
变量设置为字符串 "cartilage"
,eyelids
变量设置为布尔值 True
。类的每个实例也可以覆盖这些参数。
swim_backwards()
方法现在打印的字符串与父类 Fish
中的字符串不同,因为鲨鱼不能像骨鱼那样向后游泳。
现在我们可以创建一个 Shark
子类的实例,它仍然会使用父类 Fish
的 swim()
方法:
...
sammy = Shark("Sammy")
print(sammy.first_name + " " + sammy.last_name)
sammy.swim()
sammy.swim_backwards()
print(sammy.eyelids)
print(sammy.skeleton)
当我们运行这段代码时,我们将收到以下输出:
Sammy Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage
Shark
子类成功覆盖了父类 Fish
的 __init__()
和 swim_backwards()
方法,同时还继承了父类的 swim()
方法。
当将会有一些比其他更独特的子类时,覆盖父类方法可能会很有用。
super()
函数
使用 super()
函数,您可以访问在类对象中被覆盖的继承方法。
当我们使用 super()
函数时,我们正在在子类方法中调用父类方法,以便使用它。例如,我们可能希望覆盖父类方法的某个方面,但然后调用原始父类方法的其余部分来完成该方法。
在一个对学生进行评分的程序中,我们可能希望有一个名为 Weighted_grade
的子类,它从 Grade
父类继承。在子类 Weighted_grade
中,我们可能希望覆盖父类的 calculate_grade()
方法,以包含计算加权成绩的功能,但仍保留原始类的其余功能。通过调用 super()
函数,我们可以实现这一点。
super()
函数最常用于 __init__()
方法中,因为您很可能需要向子类添加一些独特性,然后完成父类的初始化。
为了看看这是如何工作的,让我们修改我们的 Trout
子类。由于鳟鱼通常是淡水鱼,让我们在 __init__()
方法中添加一个 water
变量,并将其设置为字符串 "freshwater"
,然后保留父类的其余变量和参数:
...
class Trout(Fish):
def __init__(self, water = "freshwater"):
self.water = water
super().__init__(self)
...
我们已经覆盖了 Trout
子类中的 __init__()
方法,提供了一个与其父类 Fish
中已定义的 __init__()
不同的实现。在我们的 Trout
类的 __init__()
方法中,我们显式调用了 Fish
类的 __init__()
方法。
因为我们已经覆盖了该方法,所以我们不再需要将 first_name
作为参数传递给 Trout
,如果我们传递参数,我们将重置 freshwater
。因此,我们将通过调用我们对象实例中的变量来初始化 first_name
。
现在我们可以调用父类的初始化变量,并且还可以使用子类的独特变量。让我们在 Trout
的一个实例中使用这个:
...
terry = Trout()
```# 初始化名字
terry.first_name = "Terry"
# 通过 super() 使用父类 __init__()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)
# 使用子类 __init__() 覆盖
print(terry.water)
# 使用父类 swim() 方法
terry.swim()
Terry Fish
False
freshwater
The fish is swimming.
输出显示,Trout
子类的对象 terry
能够同时使用子类特有的 __init__()
变量 water
,同时也能够调用 Fish
父类的 __init__()
变量 first_name
、last_name
和 eyelids
。
内置的 Python 函数 super()
允许我们在子类中覆盖某些方法的同时利用父类的方法。
多重继承
多重继承 是指一个类可以从多个父类中继承属性和方法。这可以使程序减少冗余,但也可能引入一定的复杂性和歧义,因此应该在整体程序设计时慎重考虑。
为了展示多重继承的工作原理,让我们创建一个 Coral_reef
子类,它继承自 Coral
类和 Sea_anemone
类。我们可以在每个类中创建一个方法,然后在 Coral_reef
子类中使用 pass
关键字:
class Coral:
def community(self):
print("Coral lives in a community.")
class Anemone:
def protect_clownfish(self):
print("The anemone is protecting the clownfish.")
class CoralReef(Coral, Anemone):
pass
Coral
类有一个名为 community()
的方法,打印一行内容,而 Anemone
类有一个名为 protect_clownfish()
的方法,打印另一行内容。然后我们将两个类都调用到继承元组中。这意味着 CoralReef
继承自两个父类。
现在让我们实例化一个 CoralReef
对象:
...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()
对象 great_barrier
被设置为 CoralReef
对象,并且可以使用两个父类中的方法。当我们运行程序时,将会看到以下输出:
Coral lives in a community.
The anemone is protecting the clownfish.
输出显示,子类中有效地使用了两个父类的方法。
多重继承允许我们在子类中使用来自多个父类的代码。如果在多个父类中定义了相同的方法,子类将使用其元组列表中声明的第一个父类的方法。
尽管可以有效地使用多重继承,但应该谨慎使用,以确保我们的程序不会变得模糊不清,难以让其他程序员理解。
结论
本教程介绍了构建父类和子类、在子类中覆盖父类方法和属性、使用 super()
函数以及允许子类从多个父类中继承的内容。
面向对象编程中的继承可以遵循软件开发中的 DRY(不要重复自己)原则,可以用更少的代码和重复来实现更多功能。继承还促使程序员思考他们正在创建的程序的设计方式,以确保代码是有效且清晰的。