<< >>

diveintopython.org |

Python for experienced programmers

## 3.5. 专用类方法

除了普通的类方法，还有一些对于Python类可以定义的专用方法。专用方法是在特殊情况下或当使用特别语法时由Python替你调用的，而不是在代码中直接调用(象普通的方法那样)。

就象你在上一节所看到的，普通的方法对在类中封装字典很有帮助。但是只有普通方法是不够的，因为除了对字典调用方法之外，还有很多事情可以做的。例如，你可以通过一种没有包括显式方法调用的语法来得到和设置数据项。这就是专用方法产生的原因：它们提供了一种方法，可以将非方法调用语法映射到方法调用上。

例 3.9. `__getitem__` 专用方法

        def __getitem__(self, key): return self.data[key]

    >>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
    >>> f
    {'name':'/music/_singles/kairo.mp3'}
    >>> f.__getitem__("name")
    '/music/_singles/kairo.mp3'
    >>> f["name"]
    '/music/_singles/kairo.mp3'

| `__getitem__` 专用方法很简单。象普通的方法 `clear`，`keys`，和 `values`一样，它只是重定向到字典，返回字典的值。但是怎么调用它呢？哦，你可以直接调用 `__getitem__`，但是在实际中你其实不会那样做：我在这里执行它只是要告诉你它是如何工作的。正确地使用 `__getitem__` 的方法是让Python来替你调用。
| 这个看上去就象你用来得到一个字典值的语法，事实上它返回你期望的值。下面是隐藏起来的一个环节：暗地里，Python已经将这个语法转化为 `f.__getitem__("name")` 的方法调用。这就是为什么 `__getitem__` 是一个专用类方法的原因，不仅仅是你可以自已调用它，还可以通过使用正确的语法让Python来替你调用。

例 3.10. `__setitem__` 专用方法

        def __setitem__(self, key, item): self.data[key] = item

    >>> f
    {'name':'/music/_singles/kairo.mp3'}
    >>> f.__setitem__("genre", 31)
    >>> f
    {'name':'/music/_singles/kairo.mp3', 'genre':31}
    >>> f["genre"] = 32
    >>> f
    {'name':'/music/_singles/kairo.mp3', 'genre':32}

| 象 `__getitem__` 方法一样， `__setitem__` 简单地重定向到真正的字典 `self.data` ，让它来进行工作。并且象 `__getitem__` 一样，通常你不会直接调用它，当你使用了正确的语法，Python会替你调用 `__setitem__` 。
| 这个看上去象正常的字典语法，当然除了 `f` 实际上是一个类，它尽可能地打扮成一个字典，并且 `__setitem__` 是打扮的一个重点。这行代码实际上暗地里调用了 `f.__setitem__("genre", 32)`。

`__setitem__` 是一个专用类方法，因为它可以让Python来替你调用，但是它仍然是一个类方法。就象在 `UserDict` 中定义 `__setitem__` 方法一样容易，我们可以在子类中重新定义它，对父类的方法进行覆盖。这就允许我们定义出在某些方面象字典一样动作的类，但是可以定义它自已的行为，超过和超出内置的字典。

这个概念是本章中我们正在学习的整个框架的基础。每个文件类型可以拥有一个处理器类，这些类知道如何从一个特殊的文类型得到元数据。一但知道了某些属性(象文件名和位置)，处理器类就知道如何自动地得到其它的属性。它的实现是通过覆盖 `__setitem__` 方法，检查特别的关键字，然后当找到后加入额外的处理。

例如，`MP3FileInfo` 是 `FileInfo` 的子类。在设置了一个 `MP3FileInfo` 类的 `name` 时，并不只是设置`name` 关键字(象父类 `FileInfo` 所做的)，它还要在文件自身内进行搜索MP3的标记然后填充一整套关键字集合。

例 3.11. 在 `MP3FileInfo` 中覆盖 `__setitem__`

        def __setitem__(self, key, item):
            if key == "name" and item:
                self.__parse(item)
            FileInfo.__setitem__(self, key, item)

| 注意我们的 `__setitem__` 方法严格按照父类方法相同的形式进行定义。这一点很重要，因为Python将替你执行方法，则它希望这个函数用确定个数的参数进行定义。(从技术上说，参数的名字没有关系，只是个数。)
| 这里就是整个 `MP3FileInfo` 类的难点：如果给 `name` 关键字赋一个值，我们还想做些额外的事情。
| 我们对 `name` 所做的额外处理封装在了 `__parse` 方法中。这是定义在 `MP3FileInfo` 中的另一个类方法，则当我们调用它时，我们用 `self` 对其限定。仅是调用 `__parse` 将只会看成定义在类外的普通方法，调用 `self.__parse` 将会看成定义在类中的一个类方法。这不是什么新东西，你用同样的方法来引用数据属性。
| 在做完我们额外的处理之后，我们需要调用父类的方法。记住，在Python中不会自动为你完成，需手工执行。注意，我们在调用直接父类， `FileInfo`，尽管它没有一个 `__setitem__` 方法。没问题，因为Python将会沿着父类树走，直到它找到一个有着我们正在调用方法的类，所以这行代码最终会找到并且调用定义在 `UserDict` 中的 `__setitem__`。

当在一个类中存取数据属性时，你需要限定属性名：`self.attribute`。当调用类中的其它方法时，你属要限定方法名：`self.method`。

例 3.12. 设置一个 `MP3FileInfo` 的 `name`

    >>> import fileinfo
    >>> mp3file = fileinfo.MP3FileInfo()
    >>> mp3file
    {'name':None}
    >>> mp3file["name"] = "/music/_singles/kairo.mp3"
    >>> mp3file
    {'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31,
    'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3',
    'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'}
    >>> mp3file["name"] = "/music/_singles/sidewinder.mp3"
    >>> mp3file
    {'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder',
    'name': '/music/_singles/sidewinder.mp3', 'year': '2000',
    'comment': 'http://mp3.com/cynicproject'}

| 首先，我们创建了一个 `MP3FileInfo` 的实例，没有传递给它文件名。(我们可以不用它，因为 `__init__` 方法的 `filename` 参数是可选的。)因为 `MP3FileInfo` 没有它自已的 `__init__` 方法，Python沿着父类树走，发现了 `FileInfo` 的 `__init__` 方法。这个 `__init__` 方法手工调用了 `UserDict` 的 `__init__` 方法，然后设置 `name` 关键字为 `filename`，它为 `None`，因为我们还没有传入一个文件名。所以，`mp3file` 最初看上去象有一个关键字， `name`，它的值为 `None` 的字典。
| 现在真正有趣的开始了。设置 `mp3file` 的 `name` 关键字触发了 `MP3FileInfo` 上的 `__setitem__` 方法(不是 `UserDict`)，这个方法注意到我们正在用一个真实的值来设置 `name` 关键字，接着调用 `self.__parse`。尽管我们完全还没有研究过 `__parse` 方法，从它的输出你可以看出，它设置了其它几个关键字：`album`，`artist`，`genre`，`title`，`year`，和 `comment`。
| 修改 `name` 关键字将再次经受同样的处理过程：Python调用 `__setitem__`，`__setitem__`调用 `self.__parse`，`self.__parse` 设置其它所有的关键字。

« UserDict: 一个封装类 | 高级专用类方法 »