<< >>

diveintopython.org |

Python for experienced programmers

## 3.4. `UserDict`: 一个封装类

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

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

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

例 3.7. `UserDict` `__init__` 方法

    class UserDict:
        def __init__(self, dict=None):
            self.data = {}
            if dict is not None: self.update(dict)

| 注意 `UserDict` 是一个基类，不是从任何其他类继承而来。
| 这就是我们在 `FileInfo` 类中进行了覆盖的`__init__` 方法。注意这个父类的参数列表与子类不同。某些语言(如Powerbuilder)支持通过参数列表的函数重载，也就是，同名的多个函数，有着不同个数的参数，或不同类型的参数。另外的语言(最明显如PL/SQL)甚至支持通过参数名的重载，也就是，同名的多个函数，有相同个数相同类型的参数，但是参数名字不同。Python不支持以上任何一种方式，不管怎么样它都没有函数重载的形式。一个 `__init__` 方法就是一个 `__init__` 方法，不管它有什么样的参数。每个类只能有一个 `__init__` 方法，并且如果一个子类拥有一个 `__init__` 方法，它总是覆盖父类的 `__init__` 方法，甚至子类可以用不同的参数列表来定义它。
| Python支持数据属性(在PoserBuilder中叫做“实例变量”，在C++中叫“数据成员”)，它是由某个特定的类实例所拥有的数据。在本例中，每个 `UserDict` 实例将拥有一个 `data` 数据属性。要从类外的代码引用这个属性，需要用实例的名字限定它，`instance.data`，限定的方法与你用模块的名字来限定函数一样。要在类的内部引用一个数据属性，我们使用 `self` 作为限定符。为了方便，所有的数据属性都在 `__init__` 方法中初始化为有意义的值。然而，这并不是必须的，因为数据属性，象局部变量一样，当你首次赋给它值的时候突然产生。
| 这个语法你可能以前没看过(我还没有在这本书中的例子中用过它)。这是一条 `if` 语句，但是没有在下一行有一个缩近块，而只是在冒号后面，在同一行上有单条语句。这完全是合法的，它只是当你在一个块中仅有一条语句时的一个简写。(它就象在C++中没有用大括号包括的单行语句。)你可以用这种语法，或者可以在后面的行拥有缩近代码，但是不能对同一个块同时用两种方式。

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

例 3.8. `UserDict` 普通方法

        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` 是一个普通的类方法，Python从不会替你调用，它只是象类的一般方法一样可用，你可以在任何时候调用它。注意， `clear` 象所有的类方法一样(正常或专用的)，使用 `self` 作为它的第一个参数，但是当你调用方法时，不用包括 `self` ；这件事是Python为你加上的。我知道这个有些迷惑人，但是你会完全习惯它的。还应注意这个封装类的基本技术：保存一个真正的字典作为数据属性，定义所有真正字典有拥有的方法，并且将每个类方法重定向到真正字典上的相应方法。(一旦你已经忘记了，字典的 `clear` 方法删除它的所有关键字和关键字相应的值。)
| 真正字典的 `copy` 方法会返回一个新的字典，它是原始字典的原样的复制(所有的键-值对都相同)。但是 `UserDict` 不能简单地重定向到 `self.data.copy`，因为那个方法返回一个真正的字典，而我们想要的是返回同一个类的一个新的实例，就象是 `self`。
| 我们使用 `__class__` 属性来查看是否 `self` 是一个 `UserDict`，如果是，太好了，因为我们知道如何拷贝一个 `UserDict`：只要创建一个新的 `UserDict` ，并传给它真正的字典，这个字典已经存放在 `self.data`中了。
| 如果 `self.__class__` 不是 `UserDict`，那么 `self` 一定是 `UserDict` 的某个子类(如可能为 `FileInfo`)，生活总是存在意外。 `UserDict` 不知道如何生成它的子类的一个原样的拷贝，例如，有可能在子类中定义了其它的数据属性，所以我们只能完全复制它们，确定拷贝了它们的全部内容。幸运的是，Python带了一个模块可以正确地完成这件事，它叫做 `copy`。在这里我不想深入细节(然而它是一个绝对酷的模块，是否你想到要自已研究它了呢？)。说 `copy` 能够拷贝任何Python对象就够了，这就是为什么我们在这里用它的原因。
| 其余的方法是直截了当的重定向到 `self.data` 的内置函数上。

进一步阅读 |

- Python Library Reference 提供了`UserDict` 模块和`copy` 模块的文档。

« 类的实例化 | 专用类方法 »