• 沒有找到結果。

再谈抽象

7.1 对象魔法

7.1.2 多态和方法

你收到一个对象,却根本不知道它是如何实现的——它可能是众多“形态”中的任何一种。

你只知道可以询问其价格,但这就够了。至于询问价格的方式,你应该很熟悉。

>>> object.get_price() 2.5

像这样与对象属性相关联的函数称为方法。你在本书前面见过这样的函数:字符串、列表和 字典的方法。多态你其实也见过。

>>> 'abc'.count('a') 1

>>> [1, 2, 'a'].count('a') 1

如果有一个变量x,你无需知道它是字符串还是列表就能调用方法count:只要你向这个方法 提供一个字符作为参数,它就能正常运行。

下面来做个实验。标准库模块random包含一个名为choice的函数,它从序列中随机选择一个 元素。下面使用这个函数给变量提供一个值。

>>> from random import choice

>>> x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])

执行这些代码后,x可能包含字符串'Hello, world!',也可能包含列表[1, 2, 'e', 'e', 4]。 具体是哪一个,你不知道也不关心。你只关心x包含多少个'e',而不管x是字符串还是列表你都 能找到答案。为找到答案,可像前面那样调用count。

>>> x.count('e') 2

从上述结果看,x包含的应该是列表。但关键在于你无需执行相关的检查,只要x有一个名为

count的方法,它将单个字符作为参数并返回一个整数就行。如果有人创建了包含这个方法的对

象,你也可以像使用字符串和列表一样使用这种对象。

多态形式多样

每当无需知道对象是什么样的就能对其执行操作时,都是多态在起作用。这不仅仅适用于方

1

>>> 'Fish' + 'license' 'Fishlicense'

>>> add('Fish', 'license') 'Fishlicense'

这也许有点傻,但重点在于参数可以是任何支持加法的对象。如果要编写一个函数,通过 打印一条消息来指出对象的长度,可以像下面这样做(它对参数的唯一要求是有长度,可对其执 行函数len)。

def length_message(x):

print("The length of", repr(x), "is", len(x))

如你所见,这个函数还使用了repr。repr是多态的集大成者之一,可用于任何对象,下面就 来看看:

>>> length_message('Fnord') The length of 'Fnord' is 5

>>> length_message([1, 2, 3]) The length of [1, 2, 3] is 3

① 请注意,这些对象必须支持它们之间的加法,因此调用add(1, 'license')不可行。

7.1.3 封装

封装(encapsulation)指的是向外部隐藏不必要的细节。这听起来有点像多态(无需知道对 象的内部细节就可使用它)。这两个概念很像,因为它们都是抽象的原则。它们都像函数一样,

可帮助你处理程序的组成部分,让你无需关心不必要的细节。

但封装不同于多态。多态让你无需知道对象所属的类(对象的类型)就能调用其方法,而封 装让你无需知道对象的构造就能使用它。听起来还是有点像?下面来看一个使用了多态但没有使 用封装的示例。假设你有一个名为OpenObject的类(如何创建类将在本章后面介绍)。

>>> o = OpenObject() # 对象就是这样创建的

>>> o.set_name('Sir Lancelot')

>>> o.get_name() 'Sir Lancelot'

你(通过像调用函数一样调用类)创建一个对象,并将其关联到变量o,然后就可以使用方 法set_name和get_name了(假设OpenObject支持这些方法)。一切都看起来完美无缺。然而,如果 o将其名称存储在全局变量global_name中呢?

>>> global_name 'Sir Lancelot'

这意味着使用OpenObject类的实例(对象)时,你需要考虑global_name的内容。事实上,必 须确保无人能修改它。

>>> global_name = 'Sir Gumby'

>>> o.get_name() 'Sir Gumby'

如果尝试创建多个OpenObject对象,将出现问题,因为它们共用同一个变量。

>>> o1 = OpenObject()

>>> o2 = OpenObject()

>>> o1.set_name('Robin Hood')

>>> o2.get_name() 'Robin Hood'

如你所见,设置一个对象的名称时,将自动设置另一个对象的名称。这可不是你想要的结果。

基本上,你希望对象是抽象的:当调用方法时,无需操心其他的事情,如避免干扰全局变量。

如何将名称“封装”在对象中呢?没问题,将其作为一个属性即可。

属性是归属于对象的变量,就像方法一样。实际上,方法差不多就是与函数相关联的属性

(7.2.3节将介绍方法和函数之间的一个重要差别)。如果你使用属性而非全局变量重新编写前面的 类,并将其重命名为ClosedObject,就可像下面这样使用它:

>>> c = ClosedObject()

>>> c.set_name('Sir Lancelot')

>>> c.get_name() 'Sir Lancelot'

到目前为止一切顺利,但这并不能证明名称不是存储在全局变量中的。下面再来创建一个

1 2 3 4 5

15

6