Logo
Section 1 / 13

继承的基本概念

了解 Python 中继承的基本概念。

导入

继承的基本概念

了解 Python 中继承的基本概念。

在编程世界中,重复代码被视为有害。我们不应在不同位置保留相同或相似的代码副本。当我们修复一处代码中的错误,却忘记修复另一处相同的错误时,就会给自己带来无穷无尽的问题。

有多种方式可以合并具有相似功能的代码片段或对象。在本章中,我们将介绍最著名的面向对象原则:继承。继承允许我们在两个或多个类之间建立“是一种”的关系,将共用逻辑抽象到父类中,并在每个子类中扩展具体的细节。具体来说,我们将涵盖以下内容的 Python 语法和原理:

  • 基础继承
  • 继承内置类型
  • 多重继承
  • 多态与鸭子类型

本章的案例研究将在前一章的基础上进行拓展。我们将利用继承和抽象的概念,探索如何在 k-近邻计算的不同部分中管理共用代码。

我们将首先深入分析继承的工作机制,通过提取共用特性来避免复制粘贴式的编程。

概述

从技术上讲,我们创建的每一个类都使用了继承。所有 Python 类都是名为 object 的特殊内置类的子类。这个类提供了一些元数据和基本行为,使得 Python 能够一致地处理所有对象。

如果我们没有显式地继承其他类,我们的类会自动继承自 object。不过,我们可以冗余地声明类从 object 继承,使用如下语法:

1class MySubClass(object): 2 pass

这就是继承。在 Python 3 中,如果我们不显式指定其他父类,所有类都会自动继承自 object。在这一关系中,被继承的类称为父类(或超类),例如这里的 object。而子类(如本例中的 MySubClass)则从父类继承。子类也被称为从其父类派生,或者子类扩展了父类。

正如你从示例中已经发现的,继承相比基础类定义只需添加少量额外语法。只需在类名和冒号之间用括号包含父类名称即可。这便是告诉 Python 新类应从指定父类派生的全部操作。

示例

继承在实际中如何应用?最简单且最直接的用法是为现有类添加功能。

父类

让我们从一个联系人管理器开始,用于跟踪若干人的姓名和电子邮件地址。Contact 类负责维护一个全局列表,记录所有曾经出现过的联系人,并初始化单个联系人的姓名和地址:

1class Contact: 2 all_contacts = [] 3 4 def __init__(self, name: str, email: str) -> None: 5 self.name = name 6 self.email = email 7 Contact.all_contacts.append(self) 8 9 def __repr__(self) -> str: 10 return ( 11 f"{self.__class__.__name__}(" 12 f"{self.name!r}, {self.email!r}" 13 f")" 14 )

这个例子引入了类变量的概念。all_contacts 列表由于属于类定义的一部分,因此被该类的所有实例共享。这意味着只有一个 Contact.all_contacts 列表。我们也可以在 Contact 类的任意实例方法中通过 self.all_contacts 访问它。如果某个字段在对象上找不到(通过 self),Python 会尝试在类上查找,从而指向同一个唯一的列表。

注意:使用 self 引用时要小心。它只能访问已存在的类变量。如果我们尝试通过 self.all_contacts 设置该变量,实际上会为该对象创建一个新的实例变量,而类变量本身保持不变,仍可通过 Contact.all_contacts 访问。

我们可以通过以下示例观察类如何追踪数据:

1class Contact: 2 all_contacts = [] 3 4 def __init__(self, name: str, email: str) -> None: 5 self.name = name 6 self.email = email 7 Contact.all_contacts.append(self) 8 9 def __repr__(self) -> str: 10 return ( 11 f"{self.__class__.__name__}(" 12 f"{self.name!r}, {self.email!r}" 13 f")" 14 ) 15 16c_1 = Contact("Dusty", "dusty@example.com") 17c_2 = Contact("Steve", "steve@itmaybeahack.com") 18print(Contact.all_contacts)

我们创建了两个 Contact 类的实例,并分别赋值给变量 c_1 和 c_2。当我们查看 Contact.all_contacts 类变量时,可以看到列表已更新,记录了这两个对象。

子类

这是一个简单的类,用于跟踪每个联系人的部分信息。但如果我们的一些联系人同时也是供应商,需要向他们订购物资怎么办?我们可以向 Contact 类添加 order() 方法,但这会导致客户或亲友也被误用于下单。更好的做法是创建一个新的 Supplier 类,它像 Contact 类一样工作,但额外增加一个接受尚未定义的 Order 对象的 order() 方法:

1class Supplier(Contact): 2 def order(self, order: "Order") -> None: 3 print( 4 "如果这是一个真实系统,我们会向 '{self.name}' 发送 " 5 f"'{order}' 订单" 6 )

现在测试这个类,我们会发现所有联系人都能接收姓名和邮箱作为 __init__() 方法的参数,但只有 Supplier 实例拥有 order() 方法:

1from pprint import pprint 2 3class Contact: 4 all_contacts = [] 5 6 def __init__(self, name: str, email: str) -> None: 7 self.name = name 8 self.email = email 9 Contact.all_contacts.append(self) 10 11 def __repr__(self) -> str: 12 return ( 13 f"{self.__class__.__name__}(" 14 f"{self.name!r}, {self.email!r}" 15 f")" 16 ) 17 18class Supplier(Contact): 19 def order(self, order: "Order") -> None: 20 print( 21 "如果这是一个真实系统,我们会向 '{self.name}' 发送 " 22 f"'{order}' 订单" 23 ) 24 25c_1 = Contact("Dusty", "dusty@example.com") 26c_2 = Contact("Steve", "steve@itmaybeahack.com") 27 28c = Contact("Some Body", "somebody@example.net") 29s = Supplier("Sup Plier", "supplier@example.net") 30print(c.name, c.email, s.name, s.email) 31pprint(c.all_contacts) 32# 下面这行会报错,因为 Contact 类没有 order 属性 33# c.order("I need pliers") 34s.order("I need pliers")

我们的 Supplier 类不仅能完成 Contact 类的所有功能(包括自动加入 Contact.all_contacts 列表),还能处理作为供应商所需的特殊操作。这就是继承的魅力所在。

此外,请注意 Contact.all_contacts 已经收集了所有 Contact 类的实例,以及其子类 Supplier 的实例。如果我们使用 self.all_contacts,则不会将所有对象统一归入 Contact 类,而是会让 Supplier 实例被放入 Supplier.all_contacts 中。

Python中的对象

扩展内置类型