了解Python3中的类继承

来自菜鸟教程
跳转至:导航、​搜索

介绍

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

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

先决条件

您应该在您的计算机或服务器上安装 Python 3 并设置编程环境。 如果您没有设置编程环境,您可以参考本地编程环境或服务器上的编程环境适合您的操作的安装和设置指南系统(Ubuntu、CentOS、Debian 等)

什么是继承?

Inheritance 是指一个类使用在另一个类中构造的代码。 如果我们从生物学角度考虑遗传,我们可以认为孩子从父母那里继承了某些特征。 也就是说,孩子可以继承父母的身高或眼睛颜色。 孩子也可以与父母同姓。

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

我们可以考虑一个名为 Parent 的父类,它具有 类变量 用于 last_nameheighteye_color 的子类Child 将继承自 Parent

因为 Child 子类继承自 Parent 基类,所以 Child 类可以重用 Parent 的代码,让程序员使用更少的代码行代码并减少冗余。

家长班

父类或基类创建一个模式,子类或子类可以基于该模式。 父类允许我们通过继承创建子类,而不必每次都重新编写相同的代码。 任何类都可以成为父类,因此它们各自都是功能齐全的类,而不仅仅是模板。

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

类似地,一个 Animal 类可能有 eating()sleeping() 方法,一个 Snake 子类可能包括它自己特定的 hissing() 和 [X140X ] 方法。

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

Info: 要跟随本教程中的示例代码,请通过运行 python3 命令在本地系统上打开 Python 交互式 shell。 然后,您可以通过在 >>> 提示符后添加示例来复制、粘贴或编辑示例。


我们将创建一个名为 fish.py 的新文件并从 __init__() 构造函数方法 开始,我们将使用 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 变量,因为我们知道大多数鱼都会以这个作为他们的姓。

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

鱼.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.")

我们在 Fish 类中添加了方法 swim()swim_backwards(),这样每个子类也可以使用这些方法。

因为我们将要创建的大多数鱼都被认为是 骨鱼 (因为它们的骨骼是由骨头制成的)而不是 软骨鱼 (因为它们有由软骨制成的骨架),我们可以在 __init__() 方法中添加更多属性:

鱼.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 关键字接受默认的父方法,在这种情况下我们将这样做:

鱼.py

...
class Trout(Fish):
    pass

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

鱼.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,它使用 Fish 类的每个方法,即使我们没有在 Trout 中定义这些方法儿童班。 我们只需要将 "Terry" 的值传递给 first_name 变量,因为所有其他变量都已初始化。

当我们运行程序时,我们将收到以下输出:

OutputTerry Fish
bone
False
The fish is swimming.
The fish can swim backwards.

接下来,让我们创建另一个包含自己的方法的子类。 我们将这个类称为 Clownfish,它的特殊方法将允许它与海葵一起生活:

鱼.py

...
class Clownfish(Fish):

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

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

鱼.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 对象 casey 能够使用 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() 方法,因为鲨鱼是会游泳的鱼。 让我们回顾一下这个子类:

鱼.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_name 变量现在设置为等于字符串 "Shark"skeleton 变量现在是设置等于字符串 "cartilage",并且 eyelids 变量现在设置为布尔值 True。 类的每个实例也可以覆盖这些参数。

方法 swim_backwards() 现在打印与 Fish 父类中的字符串不同的字符串,因为鲨鱼不能像硬骨鱼那样向后游泳。

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

鱼.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() 函数,我们将能够实现这一点。

super() 函数最常在 __init__() 方法中使用,因为您很可能需要在其中为子类添加一些唯一性,然后从父类完成初始化。

为了看看它是如何工作的,让我们修改我们的 Trout 子类。 由于鳟鱼通常是淡水鱼,让我们在 __init__() 方法中添加一个 water 变量并将其设置为等于字符串 "freshwater",然后保留父类的其余变量和参数:

鱼.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 的实例中使用它:

鱼.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__() 变量 water调用 first_namelast_nameeyelidsFish__init__() 变量。

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

多重继承

多重继承是指一个类可以从多个父类继承属性和方法。 这可以让程序减少冗余,但也会引入一定程度的复杂性和模糊性,因此应该考虑到整体程序设计。

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

珊瑚礁.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() 的方法可以打印另一行。 然后我们将这两个类都称为继承tuple。 这意味着 CoralReef 继承自两个父类。

现在让我们实例化一个 CoralReef 对象:

珊瑚礁.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(不要重复自己)原则,允许用更少的代码和重复完成更多工作。 继承还迫使程序员思考他们如何设计他们正在创建的程序,以确保代码有效且清晰。