💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 6.2. 与文件对象共事 * 6.2.1\. 读取文件 * 6.2.2\. 关闭文件 * 6.2.3\. 处理 I/O 错误 * 6.2.4\. 写入文件 Python 有一个内置函数,`open`,用来打开在磁盘上的文件。`open` 返回一个文件对象,它拥有一些方法和属性,可以得到被打开文件的信息,以及对被打开文件进行操作。 ## 例 6.3. 打开文件 ``` >>> 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' ``` | | | | --- | --- | | \[1\] | `open` 方法可以接收三个参数:文件名、模式和缓冲区参数。只有第一个参数 (文件名) 是必须的;其它两个是[可选的](../power_of_introspection/optional_arguments.html "4.2. 使用可选参数和命名参数")。如果没有指定,文件以文本方式打开。这里我们以二进制方式打开文件进行读取。(`print open.__doc__` 会给出所有可能模式的很好的解释。) | | \[2\] | `open` 函数返回一个对象 (到现在为止,[这一点应该不会使你感到吃惊](../getting_to_know_python/everything_is_an_object.html "2.4. 万物皆对象"))。一个文件对象有几个有用的属性。 | | \[3\] | 文件对象的 `mode` 属性告诉你文件以何种模式被打开。 | | \[4\] | 文件对象的 `name` 属性告诉你文件对象所打开的文件名。 | ## 6.2.1. 读取文件 你打开文件之后,你要做的第一件事是从中读取,正如下一个例子所展示的。 ## 例 6.4. 读取文件 ``` >>> 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 ``` | | | | --- | --- | | \[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 False >>> f.close() >>> f <closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.closed True >>> 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() ``` | | | | --- | --- | | \[1\] | 文件对象的 `closed` 属性表示对象是打开还是关闭了文件。在本例中,文件仍然打开着 (`closed` 是 `False`)。 | | \[2\] | 为了关闭文件,调用文件对象的 `close` 方法。这样就释放掉你加在文件上的锁 (如果有的话),刷新被缓冲的系统还未写入的输出 (如果有的话),并且释放系统资源。 | | \[3\] | `closed` 属性证实了文件被关闭了。 | | \[4\] | 文件被关闭了,但这并不意味着文件对象不再存在。变量 `f` 将继续存在,直到它[超出作用域](../object_oriented_framework/instantiating_classes.html#fileinfo.scope "例 5.8. 尝试实现内存泄漏")或被手工删除。然而,一旦文件被关闭,操作它的方法就没有一个能使用;它们都会引发异常。 | | \[5\] | 对一个文件已经关闭的文件对象调用 `close` _不会_ 引发异常,它静静地失败。 | ## 6.2.3. 处理 I/O 错误 现在你已经足能理解前一章的例子程序 `fileinfo.py` 的文件处理代码了。下面这个例子展示了如何安全地打开文件和读取文件,以及优美地处理错误。 ## 例 6.6. `MP3FileInfo` 中的文件对象 ``` try: fsock = open(filename, "rb", 0) try: fsock.seek(-128, 2) tagdata = fsock.read(128) finally: fsock.close() . . . except IOError: pass ``` | | | | --- | --- | | \[1\] | 因为打开和读取文件有风险,并且可能引发异常,所有这些代码都用一个 `try...except` 块封装。(嘿,[标准化的缩近](../getting_to_know_python/indenting_code.html "2.5. 代码缩进")不好吗?这就是你开始欣赏它的地方。) | | \[2\] | `open` 函数可能引发 `IOError` 异常。(可能是文件不存在。) | | \[3\] | `seek` 方法可能引发 `IOError` 异常。(可能是文件长度小于 128 字节。) | | \[4\] | `read` 方法可能引发 `IOError` 异常。(可能磁盘有坏扇区,或它在一个网络驱动器上,而网络刚好断了。) | | \[5\] | 这是新的:一个 `try...finally` 块。一旦文件通过 `open` 函数被成功地打开,我们应该绝对保证把它关闭,即使是在 `seek` 或 `read` 方法引发了一个异常时。`try...finally` 块可以用来:在 `finally` 块中的代码将_总是_ 被执行,甚至某些东西在 `try` 块中引发一个异常也会执行。可以这样考虑,不管在路上发生什么,代码都会被 “即将灭亡” 地执行。 | | \[6\] | 最后,处理我们的 `IOError` 异常。它可能是由调用 `open`、`seek` 或 `read` 引发的 `IOError` 异常。这里,我们其实不用关心,因为将要做的事就是静静地忽略它然后继续。(记住,`pass` 是一条不做任何事的 Python 语句。) 这样完全合法,“处理” 一个异常可以明确表示不做任何事。它仍然被认为处理过了,并且处理将正常继续,从 `try...except` 块的下一行代码开始。 | ## 6.2.4. 写入文件 正如你所期待的,你也能用与读取文件同样的方式写入文件。有两种基本的文件模式: * 追加 (Append) 模式将数据追加到文件尾。 * 写入 (write) 模式将覆盖文件的原有内容。 如果文件还不存在,任意一种模式都将自动创建文件,因此从来不需要任何复杂的逻辑:“如果 log 文件还不存在,将创建一个新的空文件,正因为如此,你可以第一次就打开它”。打开文件并开始写就可以了。 ## 例 6.7. 写入文件 ``` >>> logfile = open('test.log', 'w') >>> logfile.write('test succeeded') >>> logfile.close() >>> print file('test.log').read() test succeeded >>> logfile = open('test.log', 'a') >>> logfile.write('line 2') >>> logfile.close() >>> print file('test.log').read() test succeededline 2 ``` | | | | --- | --- | | \[1\] | 你可以大胆地开始创建新文件 `test.log` 或覆盖现有文件,并为写入目的而打开它。(第二个参数 `"w"` 的意思是为文件写入而打开。) 是的,它和想象中的一样危险。我希望你不要关心文件以前的内容,因为它现在已经不存在了。 | | \[2\] | 你可以使用 `open` 返回的文件对象的 `write` 方法向一个新打开的文件添加数据。 | | \[3\] | `file` 是 `open` 的同义语。这一行语句打开文件,读取内容,并打印它们。 | | \[4\] | 碰巧你知道 `test.log` 存在 (因为你刚向它写完了数据),所以你可以打开它并向其追加数据。(`"a"` 参数的意思是为追加目的打开文件。) 实际上即使文件不存在你也可以这样做,因为以追加方式打开一文件时,如果需要的话会创建文件。但是追加操作_从不_ 损坏文件的现有内容。 | | \[5\] | 正如你所看到的,原来的行和你以追加方式写入的第二行现在都在 `test.log` 中了。同时注意两行之间并没包含回车符。因为两次写入文件时都没有明确地写入回车符,所以文件中没有包含回车符。你可以用 `"\n"` 写入回车符。因为你没做这项工作,所以你写到文件的所有内容都将显示在同一行上。 | ## 进一步阅读 * _Python Tutorial_ 讨论了文件的读取和写入,包括如何[将一个文件一次一行地读到 list 中](http://www.python.org/doc/current/tut/node9.html#SECTION009210000000000000000)。 * eff-bot 讨论了[各种各样读取文件方法](http://www.effbot.org/guides/readline-performance.htm) 的效率和性能。 * Python Knowledge Base 回答了[关于文件的常见问题](http://www.faqts.com/knowledge-base/index.phtml/fid/552)。 * _Python Library Reference_ 总结了[所有文件对象模块](http://www.python.org/doc/current/lib/bltin-file-objects.html)。