6.2. 使用文件对象

Python 有一个内置函数 open,用于打开磁盘上的文件。 open 返回一个文件对象,该对象具有获取信息和操作已打开文件的方法和属性。

示例 6.3. 打开文件

>>> f = open("/music/_singles/kairo.mp3", "rb") 1
>>> f                                           2
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.mode                                      3
'rb'
>>> f.name                                      4
'/music/_singles/kairo.mp3'
1 open 方法最多可以接受三个参数:文件名、模式和缓冲参数。只有第一个参数(文件名)是必需的;其他两个是可选的。如果未指定,则以文本模式打开文件以进行读取。在这里,您将以二进制模式打开文件以进行读取。(print open.__doc__ 显示了对所有可能模式的详细说明。)
2 open 函数返回一个对象(到目前为止,这应该不会让您感到惊讶)。文件对象具有几个有用的属性。
3 文件对象的 mode 属性告诉您文件是以哪种模式打开的。
4 文件对象的 name 属性告诉您文件对象已打开的文件的名称。

6.2.1. 读取文件

打开文件后,您要做的第一件事就是从中读取数据,如下一个示例所示。

示例 6.4. 读取文件

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.tell()              1
0
>>> f.seek(-128, 2)       2
>>> f.tell()              3
7542909
>>> tagData = f.read(128) 4
>>> tagData
'TAGKAIRO****THE BEST GOA         ***DJ MARY-JANE***            
Rave Mix                      2000http://mp3.com/DJMARYJANE     \037'
>>> f.tell()              5
7543037
1 文件对象维护有关其已打开文件的状态。文件对象的 tell 方法告诉您当前在打开文件中的位置。由于您尚未对该文件执行任何操作,因此当前位置为 0,即文件的开头。
2 文件对象的 seek 方法移动到打开文件中的另一个位置。第二个参数指定第一个参数的含义;0 表示移动到绝对位置(从文件开头开始计数),1 表示移动到相对位置(从当前位置开始计数),而 2 表示移动到相对于文件末尾的位置。由于您要查找的 MP3 标签存储在文件的末尾,因此您使用 2 并告诉文件对象移动到距文件末尾 128 个字节的位置。
3 tell 方法确认当前文件位置已移动。
4 read 方法从打开的文件中读取指定数量的字节,并返回一个包含已读取数据的字符串。可选参数指定要读取的最大字节数。如果未指定参数,则 read 将读取到文件末尾。(您在这里可以简单地说 read(),因为您确切知道自己在文件中的位置,并且实际上是在读取最后 128 个字节。)读取的数据将分配给 tagData 变量,并且当前位置将根据读取的字节数进行更新。
5 tell 方法确认当前位置已移动。如果您进行计算,您会发现读取 128 个字节后,该位置已增加了 128。

6.2.2. 关闭文件

打开的文件会消耗系统资源,并且根据文件模式,其他程序可能无法访问它们。完成文件后立即关闭它们非常重要。

示例 6.5. 关闭文件

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed       1
False
>>> f.close()      2
>>> f
<closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed       3
True
>>> f.seek(0)      4
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()      5
1 文件对象的 closed 属性指示该对象是否打开了文件。在这种情况下,文件仍处于打开状态(closedFalse)。
2 要关闭文件,请调用文件对象的 close 方法。这将释放您在文件上持有的锁(如果有),刷新系统尚未实际写入的缓冲写入(如果有),并释放系统资源。
3 closed 属性确认文件已关闭。
4 文件已关闭并不意味着文件对象就不复存在了。变量 f 将继续存在,直到它超出范围或被手动删除。但是,一旦文件关闭,任何操作打开文件的方法都将失效;它们都会引发异常。
5 对文件已关闭的文件对象调用 close不会引发异常;它会静默失败。

6.2.3. 处理 I/O 错误

现在,您已经了解了足够多的知识来理解上一章中 fileinfo.py 示例代码中的文件处理代码。此示例显示了如何安全地打开和读取文件并优雅地处理错误。

示例 6.6. MP3FileInfo 中的文件对象

        try:                                1
            fsock = open(filename, "rb", 0) 2
            try:                           
                fsock.seek(-128, 2)         3
                tagdata = fsock.read(128)   4
            finally:                        5
                fsock.close()              
            .
            .
            .
        except IOError:                     6
            pass                           
1 因为打开和读取文件存在风险并且可能会引发异常,所以所有这些代码都包装在一个 try...except 块中。(嘿,标准化缩进 不错吧?这就是您开始欣赏它的地方。)
2 open 函数可能会引发 IOError。(可能是文件不存在。)
3 seek 方法可能会引发 IOError。(可能是文件小于 128 个字节。)
4 read 方法可能会引发 IOError。(可能是磁盘扇区损坏,或者它位于网络驱动器上,而网络刚刚断开。)
5 这是新的内容:try...finally 块。一旦 open 函数成功打开文件,您就希望绝对确保关闭它,即使 seekread 方法引发了异常。这就是 try...finally 块的用途:finally 块中的代码将始终执行,即使 try 块中的某些内容引发了异常。将其视为在退出时执行的代码,而不管之前发生了什么。
6 最后,您要处理 IOError 异常。这可能是由对 openseekread 的调用引发的 IOError 异常。在这里,您真的不在乎,因为您要做的只是默默地忽略它并继续。(请记住,pass 是一个什么也不做Python 语句。)这是完全合法的;“处理”异常可能意味着明确地什么也不做。它仍然算作已处理,并且处理将在 try...except 块之后的下一行代码处正常继续。

6.2.4. 写入文件

如您所料,您也可以写入文件,其方式与从中读取文件的方式大致相同。有两种基本的文件模式

  • “追加”模式会将数据添加到文件的末尾。
  • “写入”模式将覆盖文件。

如果文件不存在,则任一模式都会自动创建文件,因此永远不需要任何诸如“如果日志文件尚不存在,则创建一个新的空文件,以便您可以第一次打开它”之类的繁琐逻辑。只需打开它并开始编写即可。

示例 6.7. 写入文件

>>> logfile = open('test.log', 'w') 1
>>> logfile.write('test succeeded') 2
>>> logfile.close()
>>> print file('test.log').read()   3
test succeeded
>>> logfile = open('test.log', 'a') 4
>>> logfile.write('line 2')
>>> logfile.close()
>>> print file('test.log').read()   5
test succeededline 2
1 您首先大胆地创建新文件 test.log 或覆盖现有文件,然后打开文件以进行写入。(第二个参数 "w" 表示打开文件以进行写入。)是的,这听起来很危险。我希望您不关心该文件以前的内容,因为它现在已经消失了。
2 您可以使用 open 返回的文件对象的 write 方法将数据添加到新打开的文件中。
3 fileopen 的同义词。此单行代码打开文件,读取其内容,然后打印它们。
4 您碰巧知道 test.log 存在(因为您刚刚完成了写入),因此您可以打开它并追加到它。("a" 参数表示打开文件以进行追加。)实际上,即使文件不存在,您也可以这样做,因为打开文件以进行追加会在必要时创建文件。但是追加永远不会损害文件的现有内容。
5 如您所见,您编写的第一行和追加的第二行现在都在 test.log 中。还要注意,不包括回车符。因为您两次都没有将它们显式写入文件,所以文件不包含它们。您可以使用 "\n" 字符写入回车符。因为您没有这样做,所以您写入文件的所有内容最终都挤在同一行上。

有关文件处理的更多信息