介绍
面向对象的编程创建可重复使用的代码模式,以减少开发项目的冗余。面向对象的编程实现可回收代码的一种方式是通过继承,其中一个子类可以从另一个基础类中利用代码。
本教程将探讨Python中继承的一些主要方面,包括父母类和儿童类的运作方式、方法和属性的排除方法、如何使用super()
函数以及如何使用多重继承。
前提条件
如果您没有设置编程环境,您可以参考本地编程环境的安装和安装指南(https://www.digitalocean.com/community/tutorial_series/how-to-install-and-set-up-a-local-programming-environment-for-python-3)或适用于您的操作系统(Ubuntu, CentOS, Debian 等)的编程环境(https://www.digitalocean.com/community/tutorial_collections/how-to-install-python-3-and-set-up-a-programming-environment)。
什么是继承?
继承性是指一个班级使用在另一个班级内构建的代码。如果我们从生物学上考虑继承性,我们可以想到一个从父母那里继承某些特征的孩子。
被称为 子类或 子类的类继承了从 父母类或 基类的方法和变量。
我们可以想象一个名为父母
的父母类,其中有上一个名字
,身高
和眼睛颜色
的类变量(LINK0),而孩子
的孩子类将从父母
中继承。
由于孩子
子类是从父母
基础类继承的,所以孩子
类可以重复使用父母
的代码,允许程序员使用更少的代码行,并减少冗余。
家长班级
父母或基础类创建一个模式,可以基于孩子或子类。父母类允许我们通过继承创建儿童类,而无需每次重写相同的代码。
假设我们有一个通用的Bank_account
家长类,其中有Personal_account
和Business_account
儿童类。个人和商业账户之间的许多方法将是相似的,例如提款和存款方法,所以这些方法可能属于Bank_account
的家长类。
类似地,一个动物
类可能有eating()
和sleeping()
方法,而一个蛇
子类可能包括自己的hissing()
和slithering()
方法。
让我们创建一个鱼
母类,我们以后将使用它来构建鱼类类作为其子类。
<$>[info]
信息: 要跟进本教程中的示例代码,请在本地系统上运行python3
命令,打开Python交互壳。
我们将创建一个名为fish.py
的新文件,并开始使用 __init__()
构建方法,我们将填充每个Fish
对象或子类的first_name
和last_name
类变量。
1[label fish.py]
2class Fish:
3 def __init__(self, first_name, last_name="Fish"):
4 self.first_name = first_name
5 self.last_name = last_name
我们已经将我们的最后_名称
变量初始化为鱼
字符串,因为我们知道大多数鱼都将这个作为他们的姓氏。
再加上一些其他方法:
1[label fish.py]
2class Fish:
3 def __init__(self, first_name, last_name="Fish"):
4 self.first_name = first_name
5 self.last_name = last_name
6
7 def swim(self):
8 print("The fish is swimming.")
9
10 def swim_backwards(self):
11 print("The fish can swim backwards.")
我们已将方法 swim()
和 swim_backwards()
添加到 Fish
类中,以便每个子类都能使用这些方法。
由于我们将创建的大多数鱼被认为是骨鱼(LINK0)(因为它们有骨骼),而不是骨骼鱼(LINK1)(因为它们有骨骼骨骼),我们可以为__init__()
方法添加一些其他属性:
1[label fish.py]
2class Fish:
3 def __init__(self, first_name, last_name="Fish",
4 skeleton="bone", eyelids=False):
5 self.first_name = first_name
6 self.last_name = last_name
7 self.skeleton = skeleton
8 self.eyelids = eyelids
9
10 def swim(self):
11 print("The fish is swimming.")
12
13 def swim_backwards(self):
14 print("The fish can swim backwards.")
构建一个家长类遵循与构建任何其他类相同的方法,但我们正在考虑儿童类一旦创建这些类,将能够使用哪些方法。
儿童班级
孩子或子类是将继承从父母类的类,这意味着每个孩子类将能够使用父母类的方法和变量。
例如,一个金鱼
小班子将鱼
小班子分类,可以使用鱼
中声明的游泳()
方法,而无需声明。
也就是说,如果我们有一个名为罗姆
的儿童类和一个名为Parallelogram
的父母类,我们可以说一个罗姆
是Parallelogram
,就像一个金鱼
是鱼
一样。
儿童班的第一行看起来有点不同于非儿童班,因为您必须将家长班转入儿童班作为参数:
1class Trout(Fish):
真正的
班级是鱼类
班级的孩子,我们知道这一点是因为鱼类
一词被包含在中。
对于儿童类,我们可以选择添加更多的方法,取代现有的家长方法,或使用pass
关键字接受默认的家长方法,在这种情况下我们会这样做:
1[label fish.py]
2...
3class Trout(Fish):
4 pass
现在我们可以创建一个Trout
对象,而不必定义任何额外的方法。
1[label fish.py]
2...
3class Trout(Fish):
4 pass
5
6terry = Trout("Terry")
7print(terry.first_name + " " + terry.last_name)
8print(terry.skeleton)
9print(terry.eyelids)
10terry.swim()
11terry.swim_backwards()
我们创建了一个Trout
对象terry
,它使用了Fish
类的每个方法,尽管我们没有在Trout
儿童类中定义这些方法,我们只需要将Terry
的值传递给first_name
变量,因为所有其他变量都是初始化的。
当我们运行该程序时,我们将收到以下输出:
1[secondary_label Output]
2Terry Fish
3bone
4False
5The fish is swimming.
6The fish can swim backwards.
接下来,让我们创建另一个包括它自己的方法的儿童类,我们将这个类称为小丑鱼
,它的特殊方法将允许它与海洋鱼一起生活:
1[label fish.py]
2...
3class Clownfish(Fish):
4
5 def live_with_anemone(self):
6 print("The clownfish is coexisting with sea anemone.")
接下来,让我们创建一个小丑鱼
对象,看看它是如何工作的:
1[label fish.py]
2...
3casey = Clownfish("Casey")
4print(casey.first_name + " " + casey.last_name)
5casey.swim()
6casey.live_with_anemone()
当我们运行该程序时,我们将收到以下输出:
1[secondary_label Output]
2Casey Fish
3The fish is swimming.
4The clownfish is coexisting with sea anemone.
输出显示Clownfish
对象casey
能够使用Fish
方法__init__()
和swim()
,以及其子类方法live_with_anemone()
。
如果我们尝试在一个Trout
对象中使用live_with_anemone()
方法,我们会收到一个错误:
1[secondary_label Output]
2terry.live_with_anemone()
3AttributeError: 'Trout' object has no attribute 'live_with_anemone'
这是因为live_with_anemone()
方法只属于Clownfish
儿童类,而不是Fish
家长类。
孩子们的班级继承了他们所属的家长班级的方法,所以每个孩子班级可以在程序中使用这些方法。
超越父母的方法
到目前为止,我们已经研究了使用pass
关键字来继承所有父母类鱼
行为的Trout
儿童类,还有另一个孩子类Clownfish
,继承了所有父母类行为,并创建了自己的独特方法,是针对儿童类的。
在构建家长和儿童课程时,重要的是要记住程序设计,以免产生不必要或多余的代码。
我们将创建一个鱼
母类的鱼
儿童类,因为我们创建了鱼
类,因为我们认为我们将主要创建骨鱼,我们将不得不对鱼
类进行调整,而这是一种鱼。
鲨鱼与骨头鱼不同的是,它们有骨骼而不是骨骼的骨骼,它们也有眉毛,无法向后游泳,但是鲨鱼可以通过沉没来向后操纵。
鉴于此,我们将超越__init__()
构造方法和swim_backwards()
方法,我们不需要修改swim()
方法,因为鲨鱼是可以游泳的鱼类。
1[label fish.py]
2...
3class Shark(Fish):
4 def __init__(self, first_name, last_name="Shark",
5 skeleton="cartilage", eyelids=True):
6 self.first_name = first_name
7 self.last_name = last_name
8 self.skeleton = skeleton
9 self.eyelids = eyelids
10
11 def swim_backwards(self):
12 print("The shark cannot swim backwards, but can sink backwards.")
我们在__init__()
方法中重定义了初始化参数,所以 Last_name
变量现在被设置为等于字符串Shark
,Skeleton
变量现在被设置为等于字符串Cartilage
,而Eyelids
变量现在被设置为布尔值True
。
该方法swim_backwards()
现在打印了与鱼
母类中的一个不同的字符串,因为鲨鱼不能像骨鱼那样向后游泳。
现在我们可以创建一个Shark
儿童类的实例,它仍然会使用Fish
母类的swim()
方法:
1[label fish.py]
2...
3sammy = Shark("Sammy")
4print(sammy.first_name + " " + sammy.last_name)
5sammy.swim()
6sammy.swim_backwards()
7print(sammy.eyelids)
8print(sammy.skeleton)
当我们运行此代码时,我们将收到以下输出:
1[secondary_label Output]
2Sammy Shark
3The fish is swimming.
4The shark cannot swim backwards, but can sink backwards.
5True
6cartilage
Shark
儿童类成功地超越了Fish
母类的__init__()
和swim_backwards()
方法,同时也继承了母类的swim()
方法。
当有有限数量的儿童班级比其他班级更独特时,优越的家长班级方法可以证明是有用的。
超级( )
函数
使用super()
函数,您可以访问在类对象中被重写的继承方法。
当我们使用super()
函数时,我们将一个家长方法调用到一个孩子的方法中,以便使用它,例如,我们可能希望将父母方法的一个方面与某些功能相提并论,然后调用其余的原始家长方法来完成该方法。
在一个评级学生的程序中,我们可能希望有一个孩子类重量级
从级别
的母级级继承。在儿童类重量级
中,我们可能希望将母级方法calculate_grade()
取代,以便包括计算重量级的功能,但仍然保留原始类的其余功能性。
super()
函数最常用在__init__()
方法中,因为这就是你最有可能需要将某些独特性添加到孩子的类,然后从父母开始完成。
由于鱼通常是淡水鱼,让我们将一个水
变量添加到__init__()
方法中,并将其设置为淡水
字符串,但然后保持其余的母类变量和参数:
1[label fish.py]
2...
3class Trout(Fish):
4 def __init__(self, water = "freshwater"):
5 self.water = water
6 super().__init__(self)
7...
我们在Trout
儿童类中推翻了__init__()
方法,提供了已由其母类Fish
定义的__init__()
的不同实现,在我们Trout
类的__init__()
方法中,我们明确提到了Fish
类的__init__()
方法。
由于我们已经翻译了方法,我们不再需要将first_name
作为参数传输到Trout
,如果我们通过了参数,我们会重新设置淡水
。
现在我们可以调用父母类的初始化变量,并使用独特的子变量。
1[label fish.py]
2...
3terry = Trout()
4
5# Initialize first name
6terry.first_name = "Terry"
7
8# Use parent __init__() through super()
9print(terry.first_name + " " + terry.last_name)
10print(terry.eyelids)
11
12# Use child __init__() override
13print(terry.water)
14
15# Use parent swim() method
16terry.swim()
1[secondary_label Output]
2Terry Fish
3False
4freshwater
5The fish is swimming.
输出显示,Trout
儿童类的对象terry
能够使用儿童特定的__init__()
变量水
,同时还可以调用鱼
父母__init__()
变量first_name
,last_name
和eyelids
。
内置的Python函数super()
允许我们使用家长类方法,即使在我们的孩子类中优先考虑这些方法的某些方面。
多重遗产
多重继承是指一个类可以继承来自多个母类的属性和方法,这可以允许程序减少冗余,但也可以引入一定程度的复杂性和模糊性,因此应该考虑到整体的程序设计。
为了展示多重继承是如何工作的,让我们创建一个Coral_reef
儿童类别,而不是继承从一个Coral
类别和一个Sea_anemone
类别。
1[label coral_reef.py]
2class Coral:
3
4 def community(self):
5 print("Coral lives in a community.")
6
7class Anemone:
8
9 def protect_clownfish(self):
10 print("The anemone is protecting the clownfish.")
11
12class CoralReef(Coral, Anemone):
13 pass
Coral
类有一个名为 community()
的方法,它打印出一行,而 Anemone
类有一个名为 protect_clownfish()
的方法,它打印出另一行。
现在让我们实例化一个CoralReef
对象:
1[label coral_reef.py]
2...
3great_barrier = CoralReef()
4great_barrier.community()
5great_barrier.protect_clownfish()
对象 great_barrier
被设置为 CoralReef
对象,并且可以在两个母类中使用这些方法. 当我们运行程序时,我们会看到以下输出:
1[secondary_label Output]
2Coral lives in a community.
3The anemone is protecting the clownfish.
结果表明,在儿童类中有效地使用了来自父母两类的方法。
多重继承允许我们在儿童类中使用一个以上的家长类的代码. 如果在多个家长方法中定义了相同的方法,儿童类将使用其列表中声明的第一个家长的方法。
虽然它可以被有效地使用,但多重继承应该仔细地进行,以便我们的程序不会变得模糊和其他程序员难以理解。
结论
本教程通过构建家长类和儿童类,在儿童类中优先考虑家长方法和属性,使用super()
函数,并允许儿童类从多个家长类继承。
对象导向编码的继承可以允许遵守软件开发的DRY(不要重复)原则,允许用更少的代码和重复来完成更多工作。