在看Dive Info Python这本书,发现第五章的程序一下没看明白,就仔细的看了下,将自己的代码分析贴上来,不足之处请多多指教!
下面是代码:
"""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.
This program is part of "Dive Into Python", a free Python book for
experienced programmers. Visit http://diveintopython.org/ for the
latest version.
"""
__author__ = "Mark Pilgrim (mark@diveintopython.org)"
__version__ = "$Revision: 1.3 $"
__date__ = "$Date: 2004/05/05 21:57:19 $"
__copyright__ = "Copyright (c) 2001 Mark Pilgrim"
__license__ = "Python"
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): #4
"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): #6
"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]) #6-1
except IOError:
pass
def __setitem__(self, key, item): #5
if key == "name" and item:
self.__parse(item)
FileInfo.__setitem__(self, key, item)
def listDirectory(directory, fileExtList): #3
"get list of file info objects for files of particular extensions"
fileList = [os.path.normcase(f) for f in os.listdir(directory)] #3-1
fileList = [os.path.join(directory, f) for f in fileList \
if os.path.splitext(f)[1] in fileExtList] #3-2
def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]): #3-3
"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 #3-4
return [getFileInfoClass(f)(f) for f in fileList] #3-5
if __name__ == "__main__": # 1
for info in listDirectory("/music/_singles/", [".mp3"]): #2
print "\n".join(["%s=%s" % (k, v) for k, v in info.items()]) #7
print
下面是自己的代码分析,没有太好的整理,可能表达有些不太清楚,请谅解!
1. 根据__name__属性决定是否执行,若直接运行此脚本,则会运行下面的代码.
2.传入2个参数给listDirectory,一个为目录路径(字符串),另一个为列表,里面的元素为各种文件的后缀名,这里为’.mp3’,对返回结果进行遍历,所以我们要先看看listDirectory函数的定义。
3.看一下listDirectory的定义:
接受2个参数:第一个参数为目录路径,第二个参数(在这里其实为一个列表),里面可以包含各种文件类型的后缀名。
3-1 这里运用了列表解析,os.listdir()会列出指定目录下的所有文件,我们对os.listdir()返回的结果进行遍历,对其中的每个元素执行os.path.normcase()操作。最终将结果保存在fileList变量中,fileList的内容是一个包含指定目录下所有文件名(小写)的列表。
3-2 这里仍然运用列表解析,看一下这个语句:
fileList = [os.path.join(directory, f) for f in fileList if os.path.splitext(f)[1] in fileExtList]
for f in fileList说明是对3-1中fileList的内容进行遍历,if os.path.splitext(f)[1] in fileExtList是对fileList中的元素进行条件过滤,os.path.splitext(f)会将f以最后一个dot(.)进行分割,例如, os.path.splitext(‘a.txt’)就会分割为[‘a’, ‘.txt’], if os.path.splitext(f)[1] in fileExtList语句就是根据分割返回后结果是否在fileExtList里进行过滤的。过滤后的元素都会执行os.path.join(directory, f)操作,其实就是将路径名和文件名组合起来,最终fileList也是一个列表,其中的每个元素都是一个由目录和符合筛选条件的文件名组成的字符串。
3-3 在listDirectory函数内部我们定义了一个函数getFileInfoClass,它接受2个参数,第一个为文件名,第二个为模块名称。
subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:] 会根据filename产生一个类名,如我们的filename是.mp3格式的,则subclass = “MP3FileInfo”
3-4 注意这里:return hasattr(module, subclass) and getattr(module, subclass) or FileInfo
函数getFileInfoClass返回一个类,如果在模块中找到了subclass,就返回这个类的引用,否则返回FileInfo类的引用。
3-5 listDirectory函数的返回值为一个列表,我们在2处对这个列表进行遍历。getFileInfoClass(f)(f)会让人有些困惑,其实是这样的:因为getFileInfoClass的返回值为一个类,所以第一次getFileInfoClass(f)会返回相应的类,而getFileInfoClass(f)(f)则表示以f为参数传递给getFileInfoClass(f)返回的类。最终istDirectory函数的返回值是一个包含类实例的列表。(在这里是MP3FileInfo类的实例)
4. 我们来看下MP3FileInfo类的定义,它继承FileInfo类,而FileInfo类继承UserDict类,它们的关系如下:UserDict-->FileInfo-->MP3FileInfo
3-5处的getFileInfoClass(f)(f)实际等价于调用MP3FileInfo(f),但MP3FileInfo类并没有定义__init__函数,所以自动调用父类FileInfo的__init__函数。
先来看下FileInfo的__init__函数:
def __init__(self, filename=None):
UserDict.__init__(self)
self["name"] = filename
由于FileInfo是从UserDict继承而来的,所以在__init__中调用了父类UserDict的__init__函数,然后为实例设置了name属性,即getFileInfoClass(f)(f)处的f. 而关键的地方也就在这里,利用self[‘name’] = filename的方式为实例设置属性时会自动触发MP3FileInfo类的__setitem__方法。
5 在MP3FileInfo类的__setitem__方法中,我们发现要为关键字name设置非空的值,这时我们需要做一些其他的工作,设置一些其它显示MP3文件信息的关键字,这杯封装在一个叫做__parse的函数中。在这之后我们要继续调用父类FileInfo的__setitem__函数,由于FileInfo也没有定义__setitem__函数,所以会调用UserDict的__setitem__函数。
6. __parse为类的私有函数,在类外不能直接进行调用,这里所做的事就是读取指定文件,然后为实例生成其余几个关键字-键值对,里面也做了一些出错处理。
6-1 这里同样会触发MP3FileInfo类的__setitem__方法
7 在这里我们会对listDirectory函数返回的实例列表中的每个元素进行处理,仍然利用了列表解析,主要是利用格式化字符串将实例的每个属性生成key=value的格式,不同的键值对用换行符进行连接。