介绍
面向对象的编程创建了可重用的代码模式,以减少开发项目中的冗余。 面向对象编程实现可回收代码的一种方法是通过继承,当一个子类可以利用另一个基类的代码时。
本教程将介绍Python中继承的一些主要方面,包括父类和子类的工作原理,如何覆盖方法和属性,如何使用super()
函数以及如何使用多重继承。
什么是继承?
继承是当一个类使用另一个类中构造的代码时。 如果我们考虑到生物学方面的遗产,我们可以想到一个孩子从父母继承某些特质。 也就是说,一个孩子可以继承父母的身高或眼睛的颜色。 孩子也可以与父母分享相同的姓氏。
称为子类或子类的类从父类或基类继承方法和变量。
我们可以想到一个父类,其父类具有last_name
, height
和eye_color
的类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
子类可能包含其自己的特定的slithering()
和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.")
我们已经将swim_backwards()
swim()
和swim_backwards()
方法添加到了Fish
类中,这样每个子类也可以使用这些方法。
因为我们将要创造的大多数鱼被认为是骨鱼 (因为他们有骨骼制成的骨骼),而不是软骨鱼 (因为他们有一个由软骨制成的骨骼),我们可以添加几个更多属性到__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.")
构建父类遵循与构建任何其他类相同的方法,除了我们正在考虑一旦创建这些类,子类将能够使用哪些方法。
儿童课程
子类或子类是将从父类继承的类。 这意味着每个子类都能够使用父类的方法和变量。
例如,将Fish
类子化的Goldfish
子类将能够使用在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
,即使我们没有在Trout
小孩类中定义这些方法,也可以使用Fish
类的每一种方法。 我们只需要将"Terry"
的值传递给first_name
变量,因为所有其他变量已初始化。
当我们运行该程序时,我们将收到以下输出:
OutputTerry 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()
当我们运行该程序时,我们将收到以下输出:
OutputCasey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.
输出显示Clownfish
对象Clownfish
能够使用Fish
方法__init__()
和swim()
以及它的live_with_anemone()
的子类方法。
如果我们尝试在Trout
对象中使用live_with_anemone()
方法,我们会收到一个错误:
Outputterry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'
这是因为live_with_anemone()
方法只属于Clownfish
子类,而不是Fish
父类。
子类继承其所属的父类的方法,因此每个子类都可以使用程序中的这些方法。
覆盖父方法
到目前为止,我们已经看到了使用pass
关键字继承所有父类Fish
行为的小孩类Trout
,以及继承了所有父类行为的另一个子类Clownfish
,并创建了自己的唯一方法具体到小孩班。 然而,有时候,我们将要使用一些父类行为,但并不是全部使用。 当我们改变父类方法时,我们覆盖它们。
构建父类和子类时,重要的是要保持程序设计,以便覆盖不会产生不必要的或冗余的代码。
我们将创建一个Fish
父类的Shark
小孩类。 因为我们创造了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
了last_name
参数,所以现在将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)
当我们运行这段代码时,我们会收到以下输出:
OutputSammy 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()
函数时,我们将一个父方法调用到一个子方法中以使用它。 例如,我们可能希望使用某些功能覆盖父方法的一个方面,但是调用原始父方法的其余部分来完成该方法。
在对学生进行评分的程序中,我们可能希望拥有从Grade
父类继承的Weighted_grade
的子类。 在孩子类Weighted_grade
,我们可能需要覆盖父类的calculate_grade()
方法,以包含计算加权等级的功能,但仍保留原始类的其余功能。 通过调用super()
函数,我们可以实现这一点。
在__init__()
方法中最常使用的是super()
函数,因为这是您最有可能需要向子类添加一些唯一性,然后从父级完成初始化的位置。
要看看它是如何工作的,我们来修改我们的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()
# Initialize first name
terry.first_name = "Terry"
# Use parent __init__() through super()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)
# Use child __init__() override
print(terry.water)
# Use parent swim() method
terry.swim()
OutputTerry Fish
False
freshwater
The fish is swimming.
输出显示, Trout
子类的对象terry
能够使用特定于子类的__init__()
变量,同时也可以调用first_name
, last_name
和eyelids
的Fish
父类__init__()
变量。
内置的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()
的方法来打印另一行。 然后我们将这两个类称为继承元组 。 这意味着Coral
从两个父类继承。
现在我们来实例化一个Coral
对象:
...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()
对象great_barrier
设置为CoralReef
对象,并且可以使用两个父类中的方法。 当我们运行该程序时,我们将看到以下输出:
OutputCoral lives in a community.
The anemone is protecting the clownfish.
输出显示两个父类的方法在子类中被有效地使用。
多重继承允许我们使用子类中多个父类的代码。 如果在多个父方法中定义相同的方法,则子类将使用在其元组列表中声明的第一个父类的方法。
虽然可以有效地使用,但应该谨慎进行多重继承,以便我们的程序不会变得模糊,难以让其他程序员理解。
结论
本教程通过构建父类和子类,使用super()
函数覆盖父类方法和子类中的属性,并允许子类继承自多个父类。
面向对象编码中的继承可以允许遵守DRY(不要重复)自己的软件开发原则,允许更少的代码和重复。 继承也迫使程序员思考如何设计他们创建的程序,以确保代码是有效和清晰的。