3.5. UserDict: 一个封装类

如你所见, FileInfo 是一个象字典一样动作的类。为了进一步揭示这一点,让我们看一看在 UserDict 模块中的 UserDict 类,它是我们的 FileInfo 类的父类。没有什么特别的,类是用Python写的,并且保存在一个 .py 文件里,就象我们的代码。特别的,它保存在你的Python安装目录的 lib 目录下。

Tip

在Windows下的Python IDE中,你可以快速打开在你的库路径中的任何模块,使用 File->Locate… (Ctrl-L)。

在Python中,你不能子类化象字符串,列表和字典的内置数据类型。作为补偿,Python附带了封装类,可以模拟这些内置的数据类型的行为: UserStringUserList,和 UserDict。通过使用一个由普通和专用方法组成的联合体, UserDict 确实是一个字典的出色模仿品,但是它仅仅是一个类,同任何其它类一样,所以你可以对它进行子类化,以提供可定制的象字典的类,如 FileInfo

例 3.11. 定义 UserDict

class UserDict:                                1
    def __init__(self, dict=None):             2
        self.data = {}                         3
        if dict is not None: self.update(dict) 4
1 注意 UserDict 是一个基类,不是从任何其他类继承而来。
2

这就是我们FileInfo 类中进行了覆盖 __init__ 方法。注意这个父类的参数列表与子类不同。很好,每个子类可以拥有自已的参数集,只要使用正确的参数调用父类就可以了。这里父类有一个定义初始值的方法(通过在 dict 参数中传入一个字典),这一方法我们的 FileInfo 没有用上。

3

Python支持数据属性(在PoserBuilder中叫做“实例变量”,在C++中叫“数据成员”),它是由某个特定的类实例所拥有的数据。在本例中,每个 UserDict 实例将拥有一个 data 数据属性。要从类外的代码引用这个属性,需要用实例的名字限定它,instance.data,限定的方法与你用模块的名字来限定函数一样。要在类的内部引用一个数据属性,我们使用 self 作为限定符。习惯上,所有的数据属性都在 __init__ 方法中初始化为有意义的值。然而,这并不是必须的,因为数据属性,象局部变量一样,当你首次赋给它值的时候突然产生

4

这个语法你可能以前没看过(我还没有在这本书中的例子中用过它)。这是一条 if 语句,但是没有在下一行有一个缩近块,而只是在冒号后面,在同一行上有单条语句。这完全是合法的,它只是当你在一个块中仅有一条语句时的一个简写。(它就象在C++中没有用大括号包括的单行语句。)你可以用这种语法,或者可以在后面的行拥有缩近代码,但是不能对同一个块同时用两种方式。

Note

Java和Powerbuilder支持通过参数列表的重载,也就是一个类可以有同名的多个方法,但这些方法或者是参数个数不同,或参数的类型不同。其它语言(最明显如PL/SQL)甚至支持通过参数名的重载,也就是一个类可以有同名的多个方法,这些方法有相同类型,相同个数的参数,但参数名不同。Python两种都不支持,总之是没有任何形式的函数重载。一个 __init__ 方法就是一个 __init__ 方法,不管它有什么样的参数。每个类只能有一个 __init__ 方法,并且如果一个子类拥有一个 __init__ 方法,它总是覆盖父类的 __init__ 方法,甚至子类可以用不同的参数列表来定义它。

Note
应该总是在 __init__ 方法中给所有类的数据属性赋予一个初始值。这样做将会节省你在后面调试的时间。

例 3.12. UserDict 普通方法

    def clear(self): self.data.clear()          1
    def copy(self):                             2
        if self.__class__ is UserDict:          3
            return UserDict(self.data)         
        import copy                             4
        return copy.copy(self)                 
    def keys(self): return self.data.keys()     5
    def items(self): return self.data.items()  
    def values(self): return self.data.values()
1

clear 是一个普通的类方法,可以在任何时候被任何人公开调用。注意, clear 象所有的类方法一样(正常或专用的),使用 self 作为它的第一个参数。(记住,当你调用方法时,不用包括 self ;这件事是Python替你做的。)还应注意这个封装类的基本技术:将一个真正的字典作为数据属性保存起来,定义所有真正字典有拥有的方法,并且将每个类方法重定向到真正字典上的相应方法。(一旦你已经忘记了,字典的 clear 方法删除它的所有关键字和关键字相应的值。)

2

真正字典的 copy 方法会返回一个新的字典,它是原始字典的原样的复制(所有的键-值对都相同)。但是 UserDict 不能简单地重定向到 self.data.copy,因为那个方法返回一个真正的字典,而我们想要的是返回同一个类的一个新的实例,就象是 self

3

我们使用 __class__ 属性来查看是否 self 是一个 UserDict,如果是,太好了,因为我们知道如何拷贝一个 UserDict:只要创建一个新的 UserDict ,并传给它真正的字典,这个字典已经存放在 self.data中了。

4

如果 self.__class__ 不是 UserDict,那么 self 一定是 UserDict 的某个子类(如可能为 FileInfo),生活总是存在意外。 UserDict 不知道如何生成它的子类的一个原样的拷贝,例如,有可能在子类中定义了其它的数据属性,所以我们只能完全复制它们,确定拷贝了它们的全部内容。幸运的是,Python带了一个模块可以正确地完成这件事,它叫做 copy。在这里我不想深入细节(然而它是一个绝对酷的模块,是否你想到要自已研究它了呢?)。说 copy 能够拷贝任何Python对象就够了,这就是为什么我们在这里用它的原因。

5

其余的方法是直截了当的重定向到 self.data 的内置函数上。


进一步阅读