了解Python 3中的继承

介绍

面向对象的编程创建了可重用的代码模式,以减少开发项目中的冗余。 面向对象编程实现可回收代码的一种方法是通过继承,当一个子类可以利用另一个基类的代码时。

本教程将介绍Python中继承的一些主要方面,包括父类和子类的工作原理,如何覆盖方法和属性,如何使用super()函数以及如何使用多重继承。

什么是继承?

继承是当一个类使用另一个类中构造的代码时。 如果我们考虑到生物学方面的遗产,我们可以想到一个孩子从父母继承某些特质。 也就是说,一个孩子可以继承父母的身高或眼睛的颜色。 孩子也可以与父母分享相同的姓氏。

称为子类子类的类父类基类继承方法和变量。

我们可以想到一个父类,其父类具有last_nameheighteye_color的类eye_colorChild类将从Parent继承。

因为Child子类是从Parent基类继承的,所以Child类可以重用Parent的代码,允许程序员使用较少的代码行并减少冗余。

家长班

父类或基类创建一个可以基于哪个子类或子类的模式。 父类允许我们通过继承来创建子类,而不必每次再次编写相同的代码。 任何类都可以做成父类,所以它们是每个功能齐全的类,而不仅仅是模板。

假设我们有一个通用的Bank_account父类,它具有Personal_accountBusiness_account子类。 个人和商业账户之间的许多方法将是类似的,例如提取和存款的方法,所以这些方法可以属于Bank_account的父类。 Business_account子类将具有特定于它的方法,包括可能收集业务记录和表单的方法以及employee_identification_number变量。

类似地, Animal类可能具有eating()sleeping()方法,并且Snake子类可能包含其自己的特定的slithering()slithering()方法。

让我们创建一个Fish父类,以后我们将使用它来构造鱼的类型作为其子类。 除了特征之外,这些鱼都会有名字和姓氏。

我们将创建一个名为fish.py的新文件,并从__init__()构造方法开始 ,我们将使用每个Fish对象或子类的first_namelast_name类变量填充。

fish.py
class Fish:
    def __init__(self, first_name, last_name="Fish"):
        self.first_name = first_name
        self.last_name = last_name

我们已经使用字符串"Fish"初始化了我们的last_name变量,因为我们知道大多数鱼将以此作为其姓氏。

我们还添加一些其他方法:

fish.py
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__()方法:

fish.py
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关键字接受默认的父方法,我们将在这种情况下执行此操作:

fish.py
...
class Trout(Fish):
    pass

我们现在可以创建一个Trout对象,而不必定义任何其他方法。

fish.py
...
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类,其特殊的方法将允许它与海葵一起生活:

fish.py
...
class Clownfish(Fish):

    def live_with_anemone(self):
        print("The clownfish is coexisting with sea anemone.")

接下来,让我们创建一个Clownfish对象,看看它是如何工作的:

fish.py
...
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()方法,因为鲨鱼是可以游泳的鱼。 我们来看看这个孩子班:

fish.py
...
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_namelast_name参数,所以现在将last_name变量设置为等于字符串"Shark"skeleton变量现在设置为等于字符串"cartilage"eyelids变量现在被设置到布尔值True 类的每个实例也可以覆盖这些参数。

方法swim_backwards()现在打印与Fish父类中的不同的字符串,因为鲨鱼不能以向上的方式向后游泳。

我们现在可以创建一个Shark子类的实例,它仍然使用Fish父类的swim()方法:

fish.py
...
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"字符串,然后保留父类的其余变量和参数:

fish.py
...
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的一个例子中使用它:

fish.py
...
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_namelast_nameeyelidsFish父类__init__()变量。

内置的Python函数super()允许我们使用父类方法,即使在我们的子类中覆盖这些方法的某些方面。

多重继承

多继承是当一个类可以从多个父类继承属性和方法时。 这可以使程序减少冗余,但也可以引入一定量的复杂性和模糊性,所以应该考虑整体程序设计。

为了显示多重继承是如何工作的,我们创建一个Coral_reef子类,而不是从Coral类和Sea_anemone类继承。 我们可以在每个方法中创建一个方法,然后在Coral_reef子类中使用pass关键字:

coral_reef.py
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对象:

coral_reef.py
...
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(不要重复)自己的软件开发原则,允许更少的代码和重复。 继承也迫使程序员思考如何设计他们创建的程序,以确保代码是有效和清晰的。

赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏