理解Python 3中的类继承

本文详细介绍了Python中面向对象编程的继承机制,包括父类和子类的概念、方法覆盖、使用super()函数以及多重继承的应用,强调了DRY原则在代码复用中的重要性。
摘要由CSDN通过智能技术生成

简介

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

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

先决条件

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

什么是继承?

继承 是指一个类使用在另一个类中构建的代码。如果我们从生物学的角度来看待继承,我们可以将子类从父类那里继承某些特征。也就是说,子类可以继承父类的身高或眼睛颜色。子类也可能与父类共享相同的姓氏。

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

我们可以想象一个名为 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()slithering() 方法。

让我们创建一个名为 Fish 的父类,稍后我们将使用它来构建其子类。每种鱼都将具有名和姓以及特征。

我们将创建一个名为 fish.py 的新文件,并从 __init__() 构造方法开始,我们将为每个 Fish 对象或子类填充 first_namelast_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 子类的实例,它仍然会使用父类 Fishswim() 方法:

...
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_namelast_nameeyelids

内置的 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(不要重复自己)原则,可以用更少的代码和重复来实现更多功能。继承还促使程序员思考他们正在创建的程序的设计方式,以确保代码是有效且清晰的。

  • 45
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张无忌打怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值