您在这里:首页 > 深入 Python > 对象和面向对象 > 特殊类方法 | << >> | ||||
深入 Python从 Python 新手到专家 |
除了普通的类方法之外,Python 类还可以定义许多特殊方法。特殊方法不是由您的代码直接调用(像普通方法那样),而是在特定情况下或使用特定语法时由 Python 为您调用。
正如您在上一节中所见,普通方法在将字典包装到类中起到了很大的作用。但是,仅凭普通方法是不够的,因为除了调用方法之外,您还可以对字典执行很多操作。首先,您可以使用不包含显式调用方法的语法来获取和设置项。这就是特殊类方法的用武之地:它们提供了一种将非方法调用语法映射到方法调用的方法。
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 为您调用它。 |
当然,Python 有一个 __setitem__ 特殊方法与 __getitem__ 配合使用,如下一个示例所示。
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}
__setitem__ 是一个特殊的类方法,因为它会被自动调用,但它仍然是一个类方法。就像在 UserDict 中定义 __setitem__ 方法一样容易,您可以在后代类中重新定义它以覆盖祖先方法。这允许您定义在某些方面像字典一样工作的类,但定义了超出内置字典的自身行为。
这个概念是您在本章中学习的整个框架的基础。每种文件类型都可以有一个处理程序类,该类知道如何从特定类型的文件中获取元数据。一旦知道了一些属性(如文件的名称和位置),处理程序类就知道如何自动派生其他属性。这是通过覆盖 __setitem__ 方法、检查特定键并在找到它们时添加额外的处理来完成的。
例如,MP3FileInfo 是 FileInfo 的后代。当设置 MP3FileInfo 的 name 时,它不会像祖先 FileInfo 那样只设置 name 键;它还会在文件中查找 MP3 标签并填充一整套键。下一个示例展示了这是如何工作的。
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。 |
>>> 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(而不是 UserDict)上的 __setitem__ 方法,该方法注意到您正在使用真实值设置 name 键,并调用 self.__parse。虽然您还没有跟踪 __parse 方法,但您可以从输出中看到它设置了其他几个键:album、artist、genre、title、year 和 comment。 |
![]() |
修改 name 键将再次经历相同的过程:Python 调用 __setitem__,后者调用 self.__parse,后者设置所有其他键。 |
<< 探索 UserDict:一个包装类 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
高级特殊类方法 >> |