继承的特性

Beginner

This tutorial is from open-source community. Access the source code

简介

在这个实验中,你将了解 Python 中继承的行为。具体来说,你将重点学习 super() 函数的工作原理以及如何实现协作式继承。继承是面向对象编程中的一个基本概念,它允许类从父类继承属性和方法,以实现代码复用和构建层次化的类结构。

在这个实践体验中,你将了解 Python 中不同类型的继承,包括单继承和多继承。你还将学习如何使用 super() 函数来遍历继承层次结构,实现一个协作式多继承的实际示例,并应用这些概念来构建一个验证系统。在这个实验中创建的主要文件是 validate.py

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 中级 级别的实验,完成率为 69%。获得了学习者 100% 的好评率。

理解单继承和多继承

在这一步中,我们将学习 Python 中两种主要的继承类型:单继承和多继承。继承是面向对象编程中的一个基本概念,它允许一个类从其他类继承属性和方法。我们还将探讨当有多个候选方法时,Python 如何确定调用哪个方法,这个过程称为方法解析。

单继承

单继承是指类形成单一的继承链。可以将其想象成一个家族树,每个类只有一个直接父类。让我们创建一个示例来理解它的工作原理。

首先,在 WebIDE 中打开一个新的终端。终端打开后,输入以下命令并按回车键启动 Python 解释器:

python3

现在你已经进入了 Python 解释器,我们将创建三个形成单继承链的类。输入以下代码:

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()

class C(B):
    def spam(self):
        print('C.spam')
        super().spam()

在这段代码中,类 B 继承自类 A,类 C 继承自类 Bsuper() 函数用于调用父类的方法。

定义这些类之后,我们可以找出 Python 搜索方法的顺序。这个顺序称为方法解析顺序(Method Resolution Order,MRO)。要查看类 C 的 MRO,请输入以下代码:

C.__mro__

你应该会看到类似以下的输出:

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

这个输出表明,Python 首先在类 C 中查找方法,然后在类 B 中查找,接着在类 A 中查找,最后在基类 object 中查找。

现在,让我们创建类 C 的一个实例并调用其 spam() 方法。输入以下代码:

c = C()
c.spam()

你应该会看到以下输出:

C.spam
B.spam
A.spam

这个输出展示了 super() 在单继承链中的工作方式。当 C.spam() 调用 super().spam() 时,它调用的是 B.spam()。然后,当 B.spam() 调用 super().spam() 时,它调用的是 A.spam()

多继承

多继承允许一个类从多个父类继承。这使得一个类可以访问其所有父类的属性和方法。让我们看看在这种情况下方法解析是如何工作的。

在 Python 解释器中输入以下代码:

class Base:
    def spam(self):
        print('Base.spam')

class X(Base):
    def spam(self):
        print('X.spam')
        super().spam()

class Y(Base):
    def spam(self):
        print('Y.spam')
        super().spam()

class Z(Base):
    def spam(self):
        print('Z.spam')
        super().spam()

现在,我们将创建一个类 M,它继承自多个父类 XYZ。输入以下代码:

class M(X, Y, Z):
    pass

M.__mro__

你应该会看到以下输出:

(<class '__main__.M'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.Base'>, <class 'object'>)

这个输出显示了类 M 的方法解析顺序。Python 将按照这个顺序搜索方法。

让我们创建类 M 的一个实例并调用其 spam() 方法:

m = M()
m.spam()

你应该会看到以下输出:

X.spam
Y.spam
Z.spam
Base.spam

请注意,super() 并不只是调用直接父类的方法。相反,它遵循子类定义的方法解析顺序(MRO)。

让我们以不同的顺序指定父类来创建另一个类 N

class N(Z, Y, X):
    pass

N.__mro__

你应该会看到以下输出:

(<class '__main__.N'>, <class '__main__.Z'>, <class '__main__.Y'>, <class '__main__.X'>, <class '__main__.Base'>, <class 'object'>)

现在,创建类 N 的一个实例并调用其 spam() 方法:

n = N()
n.spam()

你应该会看到以下输出:

Z.spam
Y.spam
X.spam
Base.spam

这展示了一个重要的概念:在 Python 的多继承中,类定义中父类的顺序决定了方法解析顺序。无论从哪个类调用 super() 函数,它都会遵循这个顺序。

当你完成对这些概念的探索后,可以输入以下代码退出 Python 解释器:

exit()

使用继承构建验证系统

在这一步中,我们将使用继承构建一个实用的验证系统。继承是编程中一个强大的概念,它允许你基于现有的类创建新的类。通过这种方式,你可以复用代码,创建更有条理和模块化的程序。通过构建这个验证系统,你将了解如何使用继承来创建可复用的代码组件,并以不同的方式组合它们。

创建基础验证器类

首先,我们需要为验证器创建一个基类。为此,我们将在 WebIDE 中创建一个新文件。你可以这样操作:点击“File” > “New File”,或者使用键盘快捷键。新文件打开后,将其命名为 validate.py

现在,让我们在这个文件中添加一些代码,以创建一个基础的 Validator 类。这个类将作为我们所有其他验证器的基础。

## validate.py
class Validator:
    @classmethod
    def check(cls, value):
        return value

在这段代码中,我们定义了一个 Validator 类,其中包含一个 check 方法。check 方法接受一个值作为参数,并直接返回该值,不做任何修改。@classmethod 装饰器用于将这个方法定义为类方法。这意味着我们可以直接在类上调用这个方法,而无需创建类的实例。

添加类型验证器

接下来,我们将添加一些用于检查值类型的验证器。这些验证器将继承自我们刚刚创建的 Validator 类。回到 validate.py 文件,添加以下代码:

class Typed(Validator):
    expected_type = object
    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        return super().check(value)

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

Typed 类是 Validator 类的子类。它有一个 expected_type 属性,初始值设置为 objectTyped 类中的 check 方法会检查给定的值是否为预期的类型。如果不是,它会抛出一个 TypeError 异常。如果类型正确,它会使用 super().check(value) 调用父类的 check 方法。

IntegerFloatString 类继承自 Typed 类,并指定了它们要检查的确切类型。例如,Integer 类会检查一个值是否为整数。

测试类型验证器

现在我们已经创建了类型验证器,让我们来测试它们。打开一个新的终端,运行以下命令启动 Python 解释器:

python3

Python 解释器启动后,我们可以导入并测试我们的验证器。以下是一些用于测试的代码:

from validate import Integer, String

Integer.check(10)  ## Should return 10

try:
    Integer.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

String.check('10')  ## Should return '10'

运行这段代码时,你应该会看到类似以下的输出:

10
Error: Expected <class 'int'>
'10'

我们还可以在函数中使用这些验证器。让我们试试看:

def add(x, y):
    Integer.check(x)
    Integer.check(y)
    return x + y

add(2, 2)  ## Should return 4

try:
    add('2', '3')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

运行这段代码时,你应该会看到:

4
Error: Expected <class 'int'>

添加值验证器

到目前为止,我们已经创建了用于检查值类型的验证器。现在,让我们添加一些用于检查值本身而不是类型的验证器。回到 validate.py 文件,添加以下代码:

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        return super().check(value)

class NonEmpty(Validator):
    @classmethod
    def check(cls, value):
        if len(value) == 0:
            raise ValueError('Must be non-empty')
        return super().check(value)

Positive 验证器用于检查一个值是否为非负数。如果值小于 0,它会抛出一个 ValueError 异常。NonEmpty 验证器用于检查一个值的长度是否不为 0。如果长度为 0,它会抛出一个 ValueError 异常。

使用多继承组合验证器

现在,我们将使用多继承来组合我们的验证器。多继承允许一个类从多个父类继承。回到 validate.py 文件,添加以下代码:

class PositiveInteger(Integer, Positive):
    pass

class PositiveFloat(Float, Positive):
    pass

class NonEmptyString(String, NonEmpty):
    pass

这些新类将类型检查和值检查组合在一起。例如,PositiveInteger 类会检查一个值是否既是整数又是非负数。这里继承的顺序很重要。验证器将按照类定义中指定的顺序进行检查。

测试组合验证器

让我们测试我们的组合验证器。在 Python 解释器中,运行以下代码:

from validate import PositiveInteger, PositiveFloat, NonEmptyString

PositiveInteger.check(10)  ## Should return 10

try:
    PositiveInteger.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

try:
    PositiveInteger.check(-10)  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

NonEmptyString.check('hello')  ## Should return 'hello'

try:
    NonEmptyString.check('')  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

运行这段代码时,你应该会看到:

10
Error: Expected <class 'int'>
Error: Expected >= 0
'hello'
Error: Must be non-empty

这展示了我们如何组合验证器来创建更复杂的验证规则。

测试完成后,你可以运行以下命令退出 Python 解释器:

exit()

将验证器应用于股票类

在这一步中,我们将了解我们的验证器在实际场景中是如何工作的。验证器就像是小检查器,确保我们使用的数据符合特定规则。我们将创建一个 Stock 类。类就像是创建对象的蓝图。在这种情况下,Stock 类将代表股票市场中的一支股票,我们将使用验证器来确保其属性的值(如股票数量和价格)是有效的。

创建股票类

首先,我们需要创建一个新文件。在 WebIDE 中,创建一个名为 stock.py 的新文件。这个文件将包含我们 Stock 类的代码。现在,将以下代码添加到 stock.py 文件中:

## stock.py
from validate import PositiveInteger, PositiveFloat

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        self._shares = PositiveInteger.check(value)

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        self._price = PositiveFloat.check(value)

    def cost(self):
        return self.shares * self.price

让我们来详细分析这段代码的功能:

  1. 我们首先从 validate 模块导入 PositiveIntegerPositiveFloat 验证器。这些验证器将帮助我们确保股票数量是正整数,价格是正浮点数。
  2. 然后我们定义了一个 Stock 类。在类内部,我们有一个 __init__ 方法。当我们创建一个新的 Stock 对象时,会调用这个方法。它接受三个参数:namesharesprice,并将它们赋值给对象的属性。
  3. 我们使用属性(property)和设置器(setter)来验证 sharesprice 的值。属性是一种控制对属性访问的方式,而设置器是当我们尝试设置该属性的值时会调用的方法。当我们设置 shares 属性时,会调用 PositiveInteger.check 方法,以确保该值是正整数。同样,当我们设置 price 属性时,会调用 PositiveFloat.check 方法,以确保该值是正浮点数。
  4. 最后,我们有一个 cost 方法。这个方法通过将股票数量乘以价格来计算股票的总成本。

测试股票类

现在我们已经创建了 Stock 类,需要对其进行测试,以查看验证器是否正常工作。打开一个新的终端并启动 Python 解释器。你可以通过运行以下命令来实现:

python3

Python 解释器启动后,我们可以导入并测试 Stock 类。在 Python 解释器中输入以下代码:

from stock import Stock

## Create a valid stock
s = Stock('GOOG', 100, 490.10)
print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
print(f"Cost: {s.cost()}")

## Try setting an invalid shares value
try:
    s.shares = -10
except ValueError as e:
    print(f"Error setting shares: {e}")

## Try setting an invalid price value
try:
    s.price = "not a price"
except TypeError as e:
    print(f"Error setting price: {e}")

运行这段代码时,你应该会看到类似于以下的输出:

Name: GOOG, Shares: 100, Price: 490.1
Cost: 49010.0
Error setting shares: Expected >= 0
Error setting price: Expected <class 'float'>

这个输出表明我们的验证器按预期工作。Stock 类不允许我们为 sharesprice 设置无效的值。当我们尝试设置无效值时,会抛出一个错误,我们可以捕获并打印该错误。

理解继承的作用

使用我们的验证器的一个优点是,我们可以轻松地组合不同的验证规则。继承是 Python 中一个强大的概念,它允许我们基于现有的类创建新的类。通过多继承,我们可以使用 super() 函数调用多个父类的方法。

例如,如果我们想确保股票名称不为空,可以按照以下步骤操作:

  1. validate 模块导入 NonEmptyString 验证器。这个验证器将帮助我们检查股票名称是否不是空字符串。
  2. Stock 类中为 name 属性添加一个属性设置器。这个设置器将使用 NonEmptyString.check() 方法来验证股票名称。

这展示了继承,特别是结合 super() 函数的多继承,如何让我们构建灵活且可通过不同组合方式复用的组件。

测试完成后,你可以运行以下命令退出 Python 解释器:

exit()

总结

在本次实验中,你学习了 Python 中继承的特性,并掌握了几个关键概念。你探究了单继承和多继承之间的区别,理解了 super() 函数如何遵循方法解析顺序(Method Resolution Order,MRO),学会了实现协作式多继承,并运用继承构建了一个实用的验证系统。

你还利用继承创建了一个灵活的验证框架,并将其应用到 Stock 类这个实际示例中,这展示了继承如何创建可复用和可组合的组件。关键要点包括 super() 在单继承和多继承中的工作原理、多继承组合功能的能力,以及将属性设置器与验证器结合使用。这些概念是 Python 面向对象编程的基础,在实际应用中被广泛使用。