简介
在这个实验中,你将了解 Python 中继承的行为。具体来说,你将重点学习 super() 函数的工作原理以及如何实现协作式继承。继承是面向对象编程中的一个基本概念,它允许类从父类继承属性和方法,以实现代码复用和构建层次化的类结构。
在这个实践体验中,你将了解 Python 中不同类型的继承,包括单继承和多继承。你还将学习如何使用 super() 函数来遍历继承层次结构,实现一个协作式多继承的实际示例,并应用这些概念来构建一个验证系统。在这个实验中创建的主要文件是 validate.py。
理解单继承和多继承
在这一步中,我们将学习 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 继承自类 B。super() 函数用于调用父类的方法。
定义这些类之后,我们可以找出 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,它继承自多个父类 X、Y 和 Z。输入以下代码:
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 属性,初始值设置为 object。Typed 类中的 check 方法会检查给定的值是否为预期的类型。如果不是,它会抛出一个 TypeError 异常。如果类型正确,它会使用 super().check(value) 调用父类的 check 方法。
Integer、Float 和 String 类继承自 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
让我们来详细分析这段代码的功能:
- 我们首先从
validate模块导入PositiveInteger和PositiveFloat验证器。这些验证器将帮助我们确保股票数量是正整数,价格是正浮点数。 - 然后我们定义了一个
Stock类。在类内部,我们有一个__init__方法。当我们创建一个新的Stock对象时,会调用这个方法。它接受三个参数:name、shares和price,并将它们赋值给对象的属性。 - 我们使用属性(property)和设置器(setter)来验证
shares和price的值。属性是一种控制对属性访问的方式,而设置器是当我们尝试设置该属性的值时会调用的方法。当我们设置shares属性时,会调用PositiveInteger.check方法,以确保该值是正整数。同样,当我们设置price属性时,会调用PositiveFloat.check方法,以确保该值是正浮点数。 - 最后,我们有一个
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 类不允许我们为 shares 和 price 设置无效的值。当我们尝试设置无效值时,会抛出一个错误,我们可以捕获并打印该错误。
理解继承的作用
使用我们的验证器的一个优点是,我们可以轻松地组合不同的验证规则。继承是 Python 中一个强大的概念,它允许我们基于现有的类创建新的类。通过多继承,我们可以使用 super() 函数调用多个父类的方法。
例如,如果我们想确保股票名称不为空,可以按照以下步骤操作:
- 从
validate模块导入NonEmptyString验证器。这个验证器将帮助我们检查股票名称是否不是空字符串。 - 在
Stock类中为name属性添加一个属性设置器。这个设置器将使用NonEmptyString.check()方法来验证股票名称。
这展示了继承,特别是结合 super() 函数的多继承,如何让我们构建灵活且可通过不同组合方式复用的组件。
测试完成后,你可以运行以下命令退出 Python 解释器:
exit()
总结
在本次实验中,你学习了 Python 中继承的特性,并掌握了几个关键概念。你探究了单继承和多继承之间的区别,理解了 super() 函数如何遵循方法解析顺序(Method Resolution Order,MRO),学会了实现协作式多继承,并运用继承构建了一个实用的验证系统。
你还利用继承创建了一个灵活的验证框架,并将其应用到 Stock 类这个实际示例中,这展示了继承如何创建可复用和可组合的组件。关键要点包括 super() 在单继承和多继承中的工作原理、多继承组合功能的能力,以及将属性设置器与验证器结合使用。这些概念是 Python 面向对象编程的基础,在实际应用中被广泛使用。