导航:起始页 > Dive Into Python > 对象和面向对象 > 探索 UserDict:一个封装类 | << >> | ||||
深入 Python :Dive Into Python 中文版Python 从新手到专家 [Dip_5.4b_CPyUG_Release] |
如你所见,FileInfo 是一个有着像字典一样的行为方式的类。为了进一步揭示这一点,让我们看一看在 UserDict 模块中的 UserDict 类,它是我们的 FileInfo 类的父类。它没有什么特别的,也是用 Python 写的,并且保存在一个 .py 文件里,就像我们其他的代码。特别之处在于,它保存在你的 Python 安装目录的 lib 目录下。
在 Windows 下的 ActivePython IDE 中,你可以快速打开在你的库路径中的任何模块,使用 -> (Ctrl-L)。 |
class UserDict: def __init__(self, dict=None): self.data = {} if dict is not None: self.update(dict)
注意 UserDict 是一个基类,不是从任何其他类继承而来。 | |
这就是我们在 FileInfo 类中进行了覆盖的 __init__ 方法。注意这个父类的参数列表与子类不同。很好,每个子类可以拥有自已的参数集,只要使用正确的参数调用父类就可以了。这里父类有一个定义初始值的方法 (通过在 dict 参数中传入一个字典),这一方法我们的 FileInfo 没有用上。 | |
Python 支持数据属性 (在 Java 和 Powerbuilder 中叫做 “实例变量”,在 C++ 中叫 “数据成员”),它是由某个特定的类实例所拥有的数据。在本例中,每个 UserDict 实例将拥有一个 data 数据属性。要从类外的代码引用这个属性,需要用实例的名字限定它,instance.data,限定的方法与你用模块的名字来限定函数一样。要在类的内部引用一个数据属性,我们使用 self 作为限定符。习惯上,所有的数据属性都在 __init__ 方法中初始化为有意义的值。然而,这并不是必须的,因为数据属性,像局部变量一样,当你首次赋给它值的时候突然产生。 | |
update 方法是一个字典复制器:它把一个字典中的键和值全部拷贝到另一个字典。这个操作并不 事先清空目标字典,如果一些键在目标字典中已经存在,则它们将被覆盖,那些键名在目标字典中不存在的则不改变。应该把 update 看作是合并函数,而不是复制函数。 | |
这个语法你可能以前没看过 (我还没有在这本书中的例子中用过它)。这是一条 if 语句,但是没有在下一行有一个缩近块,而只是在冒号后面,在同一行上有单条语句。这完全是合法的,它只是当你在一个块中仅有一条语句时的一个简写。(它就像在 C++ 中没有用大括号包括的单行语句。) 你可以用这种语法,或者可以在后面的行写下缩近代码,但是不能对同一个块同时用两种方式。 |
Java 和 Powerbuilder 支持通过参数列表的重载,也就是 一个类可以有同名的多个方法,但这些方法或者是参数个数不同,或者是参数的类型不同。其它语言 (最明显如 PL/SQL) 甚至支持通过参数名的重载,也就是 一个类可以有同名的多个方法,这些方法有相同类型,相同个数的参数,但参数名不同。Python 两种都不支持,总之是没有任何形式的函数重载。一个 __init__ 方法就是一个 __init__ 方法,不管它有什么样的参数。每个类只能有一个 __init__ 方法,并且如果一个子类拥有一个 __init__ 方法,它总是 覆盖父类的 __init__ 方法,甚至子类可以用不同的参数列表来定义它。 |
Python 的原作者 Guido 是这样解释方法覆盖的:“子类可以覆盖父类中的方法。因为方法没有特殊的优先级设置,父类中的一个方法在调用同类中的另一方法时,可能其实调用到的却是一个子类中覆盖父类同名方法的方法。 (C++ 程序员可能会这样想:所有的 Python 方法都是虚函数。)”如果你不明白 (它令我颇感困惑),不必在意。我想我要跳过它。[3] |
应该总是在 __init__ 方法中给一个实例的所有数据属性赋予一个初始值。这样做将会节省你在后面调试的时间,不必为捕捉因使用未初始化 (也就是不存在) 的属性而导致的 AttributeError 异常费时费力。 |
def clear(self): self.data.clear() def copy(self): if self.__class__ is UserDict: return UserDict(self.data) import copy return copy.copy(self) def keys(self): return self.data.keys() def items(self): return self.data.items() def values(self): return self.data.values()
clear 是一个普通的类方法,可以在任何时候被任何人公开调用。注意,clear 像所有的类方法一样 (常规的或专用的),使用 self 作为它的第一个参数。(记住,当你调用方法时,不用包括 self;这件事是 Python 替你做的。) 还应注意这个封装类的基本技术:将一个真正的字典 (data) 作为数据属性保存起来,定义所有真正字典所拥有的方法,并且将每个类方法重定向到真正字典上的相应方法。(你可能已经忘了,字典的 clear 方法删除它的所有关键字和关键字相应的值。) | |
真正字典的 copy 方法会返回一个新的字典,它是原始字典的原样的复制 (所有的键-值对都相同)。但是 UserDict 不能简单地重定向到 self.data.copy,因为那个方法返回一个真正的字典,而我们想要的是返回同一个类的一个新的实例,就像是 self。 | |
我们使用 __class__ 属性来查看 self 是否是一个 UserDict,如果是,太好了,因为我们知道如何拷贝一个 UserDict:只要创建一个新的 UserDict ,并传给它真正的字典,这个字典已经存放在 self.data 中了。然后你立即返回这个新的 UserDict,你甚至于不需要在下面一行中使用 import copy。 | |
如果 self.__class__ 不是 UserDict,那么 self 一定是 UserDict 的某个子类 (如可能为 FileInfo),生活总是存在意外。UserDict 不知道如何生成它的子类的一个原样的拷贝,例如,有可能在子类中定义了其它的数据属性,所以我们只能完全复制它们,确定拷贝了它们的全部内容。幸运的是,Python 带了一个模块可以正确地完成这件事,它叫做 copy。在这里我不想深入细节 (然而它是一个绝对酷的模块,你是否已经想到要自已研究它了呢?)。说 copy 能够拷贝任何 Python 对象就够了,这就是我们在这里用它的原因。 | |
其余的方法是直截了当的重定向到 self.data 的内置函数上。 |
在 Python 2.2 之前的版本中,你不可以直接子类化字符串、列表以及字典之类的内建数据类型。作为补偿,Python 提供封装类来模拟内建数据类型的行为,比如:UserString、UserList 和 UserDict。通过混合使用普通和特殊方法,UserDict 类能十分出色地模仿字典。在 Python 2.2 和其后的版本中,你可以直接从 dict 内建数据类型继承。本书 fileinfo_fromdict.py 中有这方面的一个例子。 |
如例子中所示,在 Python 中,你可以直接继承自内建数据类型 dict,这样做有三点与 UserDict 不同。
class FileInfo(dict): "store file metadata" def __init__(self, filename=None): self["name"] = filename
[3] 实际上,这一点并不是那么难以理解。考虑两个类,base 和 child,base 中的方法 a 需要调用 self.b;而我们又在 child 中覆盖了方法 b。然后我们创建一个 child 的实例,ch。调用 ch.a,那么此时的方法 a 调用的 b 函数将不是 base.b,而是 child.b。――译注
<< 类的实例化 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
专用类方法 >> |