这一章,和此后的许多章,均讨论了面向对象的 Python 程序设计。
下面是一个完整的,可运行的 Python 程序。请阅读模块、类和函数的 doc strings,可以大概了解这个程序所做的事情和工作情况。像平时一样,不用担心你不理解的东西,这就是本章其它部分将告诉你的内容。
例 5.1. fileinfo.py
如果您还没有下载本书附带的样例程序, 可以 下载本程序和其他样例程序。
"""Framework for getting filetype-specific metadata. Instantiate appropriate class with filename. Returned object acts like a dictionary, with key-value pairs for each piece of metadata. import fileinfo info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3") print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()]) Or use listDirectory function to get info on all files in a directory. for info in fileinfo.listDirectory("/music/ap/", [".mp3"]): ... Framework can be extended by adding classes for particular file types, e.g. HTMLFileInfo, MPGFileInfo, DOCFileInfo. Each class is completely responsible for parsing its files appropriately; see MP3FileInfo for example. """ import os import sys from UserDict import UserDict def stripnulls(data): "strip whitespace and nulls" return data.replace("\00", "").strip() class FileInfo(UserDict): "store file metadata" def __init__(self, filename=None): UserDict.__init__(self) self["name"] = filename class MP3FileInfo(FileInfo): "store ID3v1.0 MP3 tags" tagDataMap = {"title" : ( 3, 33, stripnulls), "artist" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "year" : ( 93, 97, stripnulls), "comment" : ( 97, 126, stripnulls), "genre" : (127, 128, ord)} def __parse(self, filename): "parse ID3v1.0 tags from MP3 file" self.clear() try: fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() if tagdata[:3] == "TAG": for tag, (start, end, parseFunc) in self.tagDataMap.items(): self[tag] = parseFunc(tagdata[start:end]) except IOError: pass def __setitem__(self, key, item): if key == "name" and item: self.__parse(item) FileInfo.__setitem__(self, key, item) def listDirectory(directory, fileExtList): "get list of file info objects for files of particular extensions" fileList = [os.path.normcase(f) for f in os.listdir(directory)] fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList] def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]): "get file info class from filename extension" subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] return hasattr(module, subclass) and getattr(module, subclass) or FileInfo return [getFileInfoClass(f)(f) for f in fileList] if __name__ == "__main__": for info in listDirectory("/music/_singles/", [".mp3"]):print "\n".join(["%s=%s" % (k, v) for k, v in info.items()]) print
下面就是从我的机器上得到的输出。你的输出将不一样,除非,由于某些令人吃惊的巧合,你与我有着共同的音乐品味。
album= artist=Ghost in the Machine title=A Time Long Forgotten (Concept genre=31 name=/music/_singles/a_time_long_forgotten_con.mp3 year=1999 comment=http://mp3.com/ghostmachine album=Rave Mix artist=***DJ MARY-JANE*** title=HELLRAISER****Trance from Hell genre=31 name=/music/_singles/hellraiser.mp3 year=2000 comment=http://mp3.com/DJMARYJANE album=Rave Mix artist=***DJ MARY-JANE*** title=KAIRO****THE BEST GOA genre=31 name=/music/_singles/kairo.mp3 year=2000 comment=http://mp3.com/DJMARYJANE album=Journeys artist=Masters of Balance title=Long Way Home genre=31 name=/music/_singles/long_way_home1.mp3 year=2000 comment=http://mp3.com/MastersofBalan album= artist=The Cynic Project title=Sidewinder genre=18 name=/music/_singles/sidewinder.mp3 year=2000 comment=http://mp3.com/cynicproject album=Digitosis@128k artist=VXpanded title=Spinning genre=255 name=/music/_singles/spinning.mp3 year=2000 comment=http://mp3.com/artists/95/vxp
Python 有两种导入模块的方法。两种都有用,你应该知道什么时候使用哪一种方法。一种方法,import module,你已经在第
下面是 from module import 的基本语法:
from UserDict import UserDict
它与你所熟知的 import module 语法很相似,但是有一个重要的区别:UserDict 被直接导入到局部名字空间去了,所以它可以直接使用,而不需要加上模块名的限定。你可以导入独立的项或使用 from module import * 来导入所有东西。
![]() | |
Python 中的 from module import * 像 Perl 中的 use module ;Python 中的 import module 像 Perl 中的 require module 。 |
![]() | |
Python 中的 from module import * 像 Java 中的 import module.* ;Python 中的 import module 像 Java 中的 import module 。 |
例 5.2. import module vs. from module import
>>> import types >>> types.FunctionType<type 'function'> >>> FunctionType
Traceback (innermost last): File "<interactive input>", line 1, in ? NameError: There is no variable named 'FunctionType' >>> from types import FunctionType
>>> FunctionType
<type 'function'>
什么时候你应该使用 from module import?
- 如果你要经常访问模块的属性和方法,且不想一遍又一遍地敲入模块名,使用 from module import。
- 如果你想要有选择地导入某些属性和方法,而不想要其它的,使用 from module import。
- 如果模块包含的属性和方法与你的某个模块同名,你必须使用 import module 来避免名字冲突。
除了这些情况,剩下的只是风格问题了,你会看到用两种方式编写的 Python 代码。
![]() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
尽量少用 from module import * ,因为判定一个特殊的函数或属性是从哪来的有些困难,并且会造成调试和重构都更困难。 5.3.
|
![]() | |
在 |
当然,实际上大多数的类都是从其它的类继承来的,并且它们会定义自已的类方法和属性。但是就像你刚才看到的,除了名字以外,类没有什么必须要具有的。特别是,C++
例 5.4. 定义 FileInfo 类
from UserDict import UserDict class FileInfo(UserDict):![]()
![]() | 在 |
![]() | |
在 |
Python
本例演示了使用
例 5.5. 初始化 FileInfo 类
class FileInfo(UserDict): "store file metadata"def __init__(self, filename=None):
![]()
![]()
![]()
![]() | 类也可以 (并且应该) 有 |
![]() | __init__ |
![]() | 每个类方法的第一个参数,包括 |
![]() | __init__ |
![]() | |
习惯上,任何 |
当定义你自已的类方法时,你必须
噢。我知道有很多知识需要吸收,但是你要掌握它。所有的
![]() | |
__init__ |
5.4. 类的实例化
在
例 5.7. 创建 FileInfo 实例
>>> import fileinfo >>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")>>> f.__class__
<class fileinfo.FileInfo at 010EC204> >>> f.__doc__
'store file metadata' >>> f
{'name': '/music/_singles/kairo.mp3'}
![]() | 你正在创建 |
![]() | 每一个类的实例有一个内置属性,__class__,它是对象的类。(注意这个表示包括了在我机器上的实例的物理地址,你的表示不会一样。)Java |
![]() | 你可以像对函数或模块一样来访问实例的 |
![]() | 还记得什么时候 |
![]() | |
在 |
如果说创建一个新的实例是容易的,那么销毁它们甚至更容易。通常,不需要明确地释放实例,因为当指派给它们的变量超出作用域时,它们会被自动地释放。内存泄漏在
例 5.8. 尝试实现内存泄漏
>>> def leakmem(): ... f = fileinfo.FileInfo('/music/_singles/kairo.mp3')... >>> for i in range(100): ... leakmem()
![]()
对于这种垃圾收集的方式,技术上的术语叫做“引用计数”。Python
在
作为曾经读过哲学专业的一员,让我感到困惑的是,当没有人对事物进行观察时,它们就消失了,但是这确实是在
5.5. 探索 UserDict:一个封装类
如你所见,FileInfo
![]() | |
在 Windows 下的 |
例 5.9. 定义 UserDict 类
class UserDict:def __init__(self, dict=None):
self.data = {}
if dict is not None: self.update(dict)
![]()
![]()
![]() | 注意 |
![]() | 这就是我们在 |
![]() | Python |
![]() | update |
![]() | 这个语法你可能以前没看过 (我还没有在这本书中的例子中用过它)。这是一条 |
![]() | |
Java |
![]() | |
Python |
![]() | |
应该总是在 |
例 5.10. 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 |
![]() | 真正字典的 |
![]() | 我们使用 |
![]() | 如果 |
![]() | 其余的方法是直截了当的重定向到 |
![]() | |
在 |
如例子中所示,在
例 5.11. 直接继承自内建数据类型 dict
class FileInfo(dict):"store file metadata" def __init__(self, filename=None):
self["name"] = filename
5.6. 专用类方法
除了普通的类方法,Python
就像你在上一节所看到的,普通的方法对在类中封装字典很有帮助。但是只有普通方法是不够的,因为除了对字典调用方法之外,还有很多事情可以做的。例如,你可以通过一种没有包括明确方法调用的语法来获得和设置数据项。这就是专用方法产生的原因:它们提供了一种方法,可以将非方法调用语法映射到方法调用上。
例 5.12. __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__ |
![]() | 这个看上去就像你用来得到一个字典值的语法,事实上它返回你期望的值。下面是隐藏起来的一个环节:暗地里,Python |
当然,Python
例 5.13. __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}
__setitem__
这个概念是本章中我们正在学习的整个框架的基础。每个文件类型可以拥有一个处理器类,这些类知道如何从一个特殊的文类型得到元数据。只要知道了某些属性 (像文件名和位置),处理器类就知道如何自动地得到其它的属性。它的实现是通过覆盖
例如,MP3FileInfo
例 5.14. 在 MP3FileInfo 中覆盖 __setitem__
def __setitem__(self, key, item):if key == "name" and item:
self.__parse(item)
FileInfo.__setitem__(self, key, item)
![]()
![]() | 注意我们的 |
![]() | 这里就是整个 |
![]() | 我们对 |
![]() | 在做完我们额外的处理之后,我们需要调用父类的方法。记住,在 |
![]() | |
当在一个类中存取数据属性时,你需要限定属性名:self.attribute。当调用类中的其它方法时,你属要限定方法名:self.method。 |
例 5.15. 设置 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'}
![]() | 首先,我们创建了一个 |
![]() | 现在真正有趣的开始了。设置 |
![]() | 修改 |
5.7. 高级专用类方法
除了
下面的例子将展示
例 5.16. UserDict 中更多的专用方法
def __repr__(self): return repr(self.data)def __cmp__(self, dict):
if isinstance(dict, UserDict): return cmp(self.data, dict.data) else: return cmp(self.data, dict) def __len__(self): return len(self.data)
def __delitem__(self, key): del self.data[key]
![]()
![]() | __repr__ |
![]() | __cmp__ |
![]() | __len__ |
![]() | __delitem__ |
![]() | |
在 |
在这个地方,你可能会想,“所有这些工作只是为了在类中做一些我可以对一个内置数据类型所做的操作”。不错,如果你能够从像字典一样的内置数据类型进行继承的话,事情就容易多了 (那样整个
专用方法意味着任何类
![]() | |
其它的面向对象语言仅让你定义一个对象的物理模型 (“这个对象有 |
Python
5.8. 类属性介绍
你已经知道了数据属性,它们是被一个特定的类实例所拥有的变量。Python
例 5.17. 类属性介绍
class MP3FileInfo(FileInfo): "store ID3v1.0 MP3 tags" tagDataMap = {"title" : ( 3, 33, stripnulls), "artist" : ( 33, 63, stripnulls), "album" : ( 63, 93, stripnulls), "year" : ( 93, 97, stripnulls), "comment" : ( 97, 126, stripnulls), "genre" : (127, 128, ord)}
>>> import fileinfo >>> fileinfo.MP3FileInfo<class fileinfo.MP3FileInfo at 01257FDC> >>> fileinfo.MP3FileInfo.tagDataMap
{'title': (3, 33, <function stripnulls at 0260C8D4>), 'genre': (127, 128, <built-in function ord>), 'artist': (33, 63, <function stripnulls at 0260C8D4>), 'year': (93, 97, <function stripnulls at 0260C8D4>), 'comment': (97, 126, <function stripnulls at 0260C8D4>), 'album': (63, 93, <function stripnulls at 0260C8D4>)} >>> m = fileinfo.MP3FileInfo()
>>> m.tagDataMap {'title': (3, 33, <function stripnulls at 0260C8D4>), 'genre': (127, 128, <built-in function ord>), 'artist': (33, 63, <function stripnulls at 0260C8D4>), 'year': (93, 97, <function stripnulls at 0260C8D4>), 'comment': (97, 126, <function stripnulls at 0260C8D4>), 'album': (63, 93, <function stripnulls at 0260C8D4>)}
![]() | |
在 |
类属性可以作为类级别的常量来使用 (这就是为什么我们在
![]() | |
在 |
例 5.18. 修改类属性
>>> class counter: ... count = 0... def __init__(self): ... self.__class__.count += 1
... >>> counter <class __main__.counter at 010EAECC> >>> counter.count
0 >>> c = counter() >>> c.count
1 >>> counter.count 1 >>> d = counter()
>>> d.count 2 >>> c.count 2 >>> counter.count 2
5.9. 私有函数
与大多数语言一样,Python
- 私有函数不可以从它们的模块外面被调用
- 私有类方法不能够从它们的类外面被调用
- 私有属性不能够从它们的类外面被访问
与大多数的语言不同,一个
如果一个
在
![]() | |
在 |
例 5.19. 尝试调用一个私有方法
>>> import fileinfo >>> m = fileinfo.MP3FileInfo() >>> m.__parse("/music/_singles/kairo.mp3")Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'MP3FileInfo' instance has no attribute '__parse'
5.10. 小结
实打实的对象把戏到此为止。你将在
下一章将继续使用本章的例程探索其他
在研究下一章之前,确保你可以无困难地完成下面的事情:
- 使用
import module 或 from module import导入模块 - 定义和实例化类
- 定义
__init__ 方法和其他专用类方法,并理解它们何时会调用 - 子类化
UserDict 来定义行为像字典的类 - 定义数据属性和类属性,并理解它们之间的不同
- 定义私有属性和方法