Linux 版 (精华区)
发信人: emacs (被淹死的鱼), 信区: Linux
标 题: 第三章 面向对象框架 -- 3
发信站: 哈工大紫丁香 (Sat Jun 15 09:53:25 2002) , 转信
3.10. 文件对象
Python有一个内置函数,open,用来打开在磁盘上的文件。open 返回一个文件对象,它拥
有一些方法和属性,可以得到打开文件的信息,和对打开文件进行操作。
例 3.19. 打开文件
>>> f = open("/music/_singles/kairo.mp3", "rb")
>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.mode
'rb'
>>> f.name
'/music/_singles/kairo.mp3' open 方法可以接收三个参数:文件名,模式,和缓冲区
参数。只有第一个参数,文件名,是必须的;其它两个是可选的。如果没有指定,文件以
文本方式打开。这里我们以二进制方式打开文件进行读取。(print open.__doc__ 会给出
所有可能模式的很好的解释。)
open 函数返回一个对象(到现在为止,这一点应该不会使你感到吃惊)。一个文件对象有
几个有用的属性。
文件对象的 mode 属性告诉你文件以何种模式被打开。
文件对象的 name 属性告诉你文件对象所打开的文件名。
例 3.20. 读取文件
>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.tell()
0
>>> f.seek(-128, 2)
>>> f.tell()
7542909
>>> tagData = f.read(128)
>>> tagData
'TAGKAIRO****THE BEST GOA ***DJ MARY-JANE*** Rave Mix
2000http://mp3.com/DJMARYJANE \037'
>>> f.tell()
7543037 一个文件对象维护它所打开文件的状态。文件对象的 tell 方法告诉你在打开文
件中的当前位置。因为我们还没有对这个文件做任何事,当前位置为 0,它是文件的开始
处。
文件对象的 seek 方法在打开文件中移动到另一个位置。第二个参数指出第一个参数是
什么意思:0 表示移动到一个绝对位置(从文件开始算起),1 表示移到一个相对位置(从当
前位置算起),还有 2 表示对于文件尾的一个相对位置。因为我们搜索的MP3标记保存在文
件的末尾,我们使用 2 并且告诉文件对象从文件尾移动到 128 字节的位置。
tell 方法确认了已经移到当前文件位置。
read 方法从打开文件中读取指定个数的字节,并且返回含有读取数据的字符串。可选参
数指定了读取的最大字节数。如果没有指定参数,read 将读到文件末尾。(我们本可以在
这里简单地说一下 read(),因为我们确切地知道在文件的何处,事实上,我们读的是最后
128个字节。)读出的数据赋给变量 tagData,并且当前的位置根据所读的字节数作了修改
。
tell 方法确认了当前位置已经移动了。如果做一下算术,你会看到在读了128个字节之
后,位置数已经增加了128。
例 3.21. 关闭文件
>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed
0
>>> f.close()
>>> f
<closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed
1
>>> f.seek(0)
Traceback (innermost last):
File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.tell()
Traceback (innermost last):
File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.read()
Traceback (innermost last):
File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.close() 文件对象的 closed 属性表示对象是否打开或关闭了文件。在本例中,
文件仍然打开着(closed 是 0)。打开文件要消耗系统资源,并且根据文件模式,其它程序
可能不能使用它们。一旦你处理完它们,把文件关闭这一点很重要。
为了关闭文件,调用文件对象的 close 方法。这样就释放掉你加在文件上的锁(如果有
的话),刷新被缓冲的系统确实还未写入的输出(如果有的话),并且翻放系统资源。close
d 属性证实了文件被关闭了。
只是因为文件被关闭,并不意味着文件对象停止存在。变量 f 将继续存在,直到它超出
作用域或被手工删除。然而,一旦文件被关闭,可操作打开文件的方法没有一个能使用;
它们都会引发异常。
对一个文件已经关闭的文件对象调用 close 不会引发异常,它静静地失败。
例 3.22. MP3FileInfo 中的文件对象
try:
fsock = open(filename, "rb", 0)
try:
fsock.seek(-128, 2)
tagdata = fsock.read(128)
finally:
fsock.close()
.
.
.
except IOError:
pass 因为打开和读取文件有风险,并且可能
引发异常,所有这些代码都用一个 try...except 块封装。(嘿,标准化的缩排不好吗?这
就是你开始欣赏它的地方。)
open 函数可能引发 IOError 异常。(可能是文件不存在。)
seek 方法可能引发 IOError 异常。(可能是文件长度小于128字节。)
read 方法可能引发 IOError 异常。(可能磁盘有坏扇区,或它在一个网络驱劝器上,而
网络刚好断了。)
这是新的:一个 try...finally 块。一旦文件通过 open 函数被成功地打开,我们应该
绝对保证把它关闭,甚至由于 seek 或 read 方法引发了一个异常。try...finally 块可
以用来:在 finally 块中的代码将总被执行,甚至某些东西在 try 块中引发一个异常也
会执行。可以这样考虑,不管在路上发生什么,代码都会被“即将灭亡”地执行。
最后,处理我们的 IOError 异常。它可能是由调用 open,seek,或 read 引发的 IOE
rror 异常。这里,我们其实不关心,因为将要做的事就是静静地忽略它然后继续。(记住
,pass 是一条不做任何事的Python语句。)这样完全合法,“处理”一个异常可以明确表
示不做任何事。它仍然被认为处理过了,并且处理将正常继续,从 try...except 块的下
一行代码。
3.11. for 循环
象其它大多数语言,Python也拥有 for 循环。你到现在还未曾看到它们的唯一的原因就是
,Python在其它太多的方面表现出色,通常你不需要它们。
其它大多数语言没有象Python一样的强大的列表数据类型,所以你需要亲自做很多事情,
指定开始,结束和步长,来定义一定范围的整数或字符或其它可重复的实体。但是在Pyth
on中,for 循环简单地在一个列表上循环,与映射列表的工作方式相同。
例 3.23. for 循环介绍
>>> li = ['a', 'b', 'e']
>>> for s in li:
... print s
a
b
e
>>> print "\n".join(li)
a
b
e for 循环的语法同映射列表相似。li 是一个列表,而 s 将从第一个元素开始依次接收
每个元素的值。
象 if 语句或其它任意缩近块,for 循环可以在它里面有任意条数的代码行。
这就是为什么你以前没看到过 for 循环的原因:至今我们都不需要它。太令人吃惊了,
当你想要的只是一个 join 或是列表映射时,而用其它语言常常需要使用 for 循环。
3.12. 一次赋多个值
在Python中最酷的程序简写之一就是使用序列一次赋多个值。
例 3.24. 一次赋多个值
>>> v = ('a', 'b', 'e')
>>> (x, y, z) = v
>>> x
'a'
>>> y
'b'
>>> z
'e' v 是一个三元素的序列,(x, y, z) 是一个有三个变量的序列。将一个序列赋给另一
个,会将 v 的每个值依次赋给每个变量。
它有着许多的用处。当构建可重用的模块时,你经常需要给一个名字赋以一系列的值。在
C或C++中,你将使用 enum 并且手工地列出每个常量和它所对应的值,当值是连续的时候
显得特别烦琐。在Python中,你可以使用内置的 range 函数来迅速地给多个变量赋予连续
值。
例 3.25. 赋连续值
>>> range(7)
[0, 1, 2, 3, 4, 5, 6]
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7
)
>>> MONDAY
0
>>> TUESDAY
1
>>> SUNDAY
6 内置的 range 函数返回一个整数列表。在这个最简单的形式中,它接受一个上限,并
返回一个从0开始计数但不包括上限的一个列表。(如果你喜欢,你可以传递其它的参数来
指定一个0以外的基数和1以外的步长。可以执行 print range.__doc__ 来了解更多细节。
)
MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,和 SUNDAY 是我们定义
的变量。(这个例子来自 calendar 模块,一个有趣的用来打印日历的小模块,就象UNIX程
序 cal。calendar 模块定义了星期每天的整数常量。)
现在每个变量都有了它的值:MONDAY 是 0,TUESDAY 是 1,等等。
使用这种技术,你可以构建返回多值的函数,只要通过返回包含所有值的一个序列。调用
者可以把返回值看成一个序列,或者将值赋给单个变量。
例 3.26. 从函数中返回多值
>>> import os
>>> os.path.split("/music/ap/mahadeva.mp3")
('/music/ap', 'mahadeva.mp3')
>>> (filepath, filename) = os.path.split("/music/ap/mahadeva.mp3")
>>> filepath
'/music/ap'
>>> filename
'mahadeva.mp3'
>>> (shortname, extension) = os.path.splitext(filename)
>>> shortname
'mahadeva'
>>> extension
'.mp3' os 模块有许多有用的函数,可用来操作文件和目录,并且 os.path 拥有操作文
件路径的函数。split 函数可以分割整个路径,并返回一个包括路径和文件名的序列。
我们将 split 函数的返回值赋给一个有两个变量的序列。每个变量从返回的序列中接收
相对应的值。
第一个变量,filepath,接收从 split 返回序列的第一个元素的值,文件路径。
第二个变量,filename, 接收从 split 返回序列的第二个元素的值,文件名。
os.path 还包含了一个 splitext 函数,它可以分割一个文件名,返回一个包含文件名
和文件扩展名的序列。我们使用同样的技术来将它们各自赋值给个别的变量。
只要有可能,你最好使用在 os 和 os.path 中的函数来处理对文件,目录,和路径的操作
。这些模块是对于平台特定模块的封装产物,所以象 os.path.split 之类的函数可以工作
在UNIX,Windows,MacOS,和其它任何支持Python的平台上。
利用多变量赋值甚至有更多可以做的。它可以用在遍历一个序列列表的时候,意味着你可
将它用于 for 循环和列表映射中。你也许不认为序列列表是你每天都要遇到的东西,但是
实际上字典的 items 方法就返回一个序列列表,每个序列的形式为(key,value)。所以多
变量赋值允许你通过简单的方法来遍历字典的元素。
例 3.27. 遍历字典
>>> for k, v in os.environ.items()
... print "%s=%s" % (k, v)
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim
[…snip…]
os.environ 是一个在你的系统中所定义的环境变量的字典。在Windows下,它们是你的
用户和系统变量,可以容易地从MS-DOS中得到。在UNIX下,它们是输出(export)到你的sh
ell启动脚本的变量。在MacOS下,没有环境变量的概念,所以这个字典为空。
os.environ.items() 返回一个序列列表:[(key1,value1),(key2,value2),...]。
for 循环遍历这个列表。第一轮,它将 key1 赋给 k 而 value1 赋给 v,所以 k = USER
PROFILE 而 v = C:\Documents and Settings\mpilgrim。第二轮,k 得到第二个关键字,
OS,而 v 得到相对的值,Windows_NT。
使用多变量赋值不是绝对必需的。它是一种方便的简写,且可以让你的代码更加可读,特
别是当处理字典时(通过 items 方法)。但是如果发现你迫使自已的代码通过种种周折(为
了以正确的形式得到数据),只是为了让你可以一次给两个变量赋值,可能就不值得那么做
了。
例 3.28. 通过多变量赋值进行字典映射
>>> print "\n".join(["%s=%s" % (k, v) for k, v in os.environ.items()])
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim
[…snip…] 多变量赋值也可以用于列表映射,使用这种简捷的方法来将字典映射成列表
。本例中,我们通过将列表连接成一个字符串使得这种用法更深一步。注意它的输出与前
例中的 for 循环一样。这就是为什么你在Python中看到那么少的 for 循环的原因;许多
复杂的事情可以不用它们完成。你可以讨论是否这种方法更易读,但是它相当快,因为只
有一条输出语句而不是许多。
例 3.29. 在 MP3FileInfo 中的多变量 for 循环
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)}
.
.
.
if tagdata[:3] == "TAG":
for tag, (start, end, parseFunc) in self.tagDataMap.items():
self[tag] = parseFunc(tagdata[start:end])
tagDataMap 是一个类属性,它定义了我们正在一个MP3文件中所查找的标记。标记被保存
在定长的字段中;一旦我们读出文件的最后128个字节,字节3到32是歌曲的题目,33-62是
歌手名字,63-92是专集名字,等等。注意 tagDataMap 是一个序列字典,每个序列包含两
个整数和一个函数引用。
这个看上去有些复杂,其实不是。for 变量结构与通过 items 返回的列表元素的结构相
匹配。记住,items 返回一个形式为(key,value)的序列列表。列表的第一个元素是("ti
tle", (3, 33, <function stripnulls>)),所以循环的第一轮,tag 得到 "title",sta
rt 得到 3,end 得到 33,而 parseFunc 得到函数 stripnulls。
现在我们已经提取出了单个MP3标记的所有参数,保存标记数据很容易。我们从 start
到end 划分 tagdata 以得到这个标记的实际数据,调用 parseFunc 来对数据进行后续处
理,然后将它作为关键字的值赋给伪字典 self 的 tag 关键字。在遍历了 tagDataMap 中
所有元素之后, self 拥有所有标记的值,并且你知道那看上去象什么。
3.13. 更多关于模块
抱歉,你已经到了书的末尾,这本书就写到这里了。请回头检查http://diveintopython.
org/看看是否有变化。
--
※ 来源:.哈工大紫丁香 http://bbs.hit.edu.cn [FROM: 211.93.34.115]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:210.768毫秒