>>> from env_helper import info; info()
页面更新时间: 2022-03-22 15:15:22
运行环境:
    Linux发行版本: Debian GNU/Linux 11 (bullseye)
    操作系统内核: Linux-5.10.0-11-amd64-x86_64-with-glibc2.31
    Python版本: 3.9.2

2.2. 文件读写过程

文件最重要的功能是提供和接收数据。 如果有一个名为 f 的类似于文件的对象, 可使用 f.write 来写入数据,还可使用 f.read 来读取数据。 与Python的其他大多数功能一样,在哪些东西可用作数据方面,也存在一定的灵活性, 但在文本和二进制模式下,基本上分别将 strbytes 类用作数据。

在熟悉了处理文件夹和相对路径后,你就可以指定文件的位置, 进行读写。接下来几节介绍的函数适用于纯文本文件。 “纯文本文件”只包含基本文本字符,不包含字体、 大小和颜色信息。带有 .txt 扩展名的文本文件, 以及带有 .py 扩展名的 Python 脚本文件, 都是纯文本文件的例子。 它们可以被 Windows 的 Notepad 或 OS X 的 TextEdit 应用打开。你的程序可以轻易地读取纯文本文件的内容, 将它们作为普通的字符串值。

“二进制文件”是所有其他文件类型,诸如字处理文档、 PDF、图像、电子表格和可执行程序。 如果用 NotepadTextEdit 打开一个二进制文件, 它看起来就像乱码。

既然每种不同类型的二进制文件,都必须用它自己的方式来处理,本书就不会探讨直接读写二进制文件。 好在,许多模块让二进制文件的处理变得更容易。 在本章稍后,你将探索其中一个模块: shelve

2.2.1. open() 函数打开文件

open() 将会返回一个 file 对象,基本语法格式如下:

open(filename, mode)
  • filename :是一个包含了你要访问的文件名称的字符串值。

  • mode :决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读( r )。

不同模式打开文件的完全列表:

  • r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。

  • rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。

  • r+ 打开一个文件用于读写。文件指针将会放在文件的开头。

  • rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。

  • w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

  • wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

  • w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

  • wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。

  • a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。

  • ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。

  • a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。

  • ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

以下实例将字符串写入到文件 foo.txt 中:

要用 open() 函数打开一个文件, 就要向它传递一个字符串路径,表明希望打开的文件。 这既可以是绝对路径,也可以是相对路径。 open() 函数返回一个File对象。

尝试一下,先用 NotepadTextEdit 创建一个文本文件, 名为 hello.txt 。输入 Hello world! 作为该文本文件的内容, 将它保存在你的用户文件夹中。然后, 如果使用 Windows ,在交互式环境中输入以下代码:

helloFile =open('hello.txt')

如果使用OS X,在交互式环境中输入以下代码:

>>>     helloFile = open('hello.txt')

请确保用你自己的计算机用户名取代 your _ home _ folder 。 例如,我的用户名是 asweigart ,所以我在windows下输入 'C:\\Users\\asweigart\\hello.txt'

这些命令都将以读取纯文本文件的模式打开文件, 或简称为“读模式”。当文件以读模式打开时, Python只让你从文件中读取数据,你不能以任何方式写入或修改它。 在Python中打开文件时,读模式是默认的模式。 但如果你不希望依赖于Python的默认值, 也可以明确指明该模式,向 open() 传入字符串 'r' , 作为第二个参数。所以 open('/Users/asweigart/hello.txt','r')open('/Users/asweigart/hello.txt') 做的事情一样。

调用 open() 将返回一个File对象。 File 对象代表计算机中的一个文件, 它只是Python中另一种类型的值, 就像你已熟悉的列表和字典。 在前面的例子中,你将 File对象保存在helloFile 变量中。 现在,当你需要读取或写入该文件, 就可以调用 helloFile 变量中的 File 对象的方法。

2.2.2. 读取文件内容

既然有了一个 File 对象,就可以开始从它读取内容。 如果你希望将整个文件的内容读取为一个字符串值, 就使用 File 对象的 read()方法。 让我们继续使用保存在 helloFile 中的 hello.txt File 对象。在交互式环境中输入以下代码:

>>> helloFile = open('hello.txt')
>>> helloContent = helloFile.read()
>>> helloContent
'Hello world!'

如果你将文件的内容看成是单个大字符串,read()方法就返回保存在该文件中的这个字符串。 或者,可以使用readlines()方法,从该文件取得一个字符串的列表。 列表中的每个字符串就是文本中的每一行。

>>> import pprint
>>> pprint.pprint(open(r'somefile.txt').readlines())
['01234Hello, World!89']

请注意,这里我利用了文件对象将被自动关闭这一事实。下面来尝试写入,首先是write(string)。

>>> f = open(r'somefile.txt', 'w')
>>> f.write('this\nis no\nhaiku')
>>> f.close()

代码清单11 -4 修改后的文本文件

this
is no
haiku

最后是writelines(list):

>>> f = open(r'somefile.txt')
>>> lines = f.readlines()
>>> f.close()
>>> lines[1] = "isn't a\n"
>>> f = open(r'somefile.txt', 'w')
>>> f.writelines(lines)
>>> f.close()

代码清单11 -5 再次修改后的文本文件

this
isn't a
haiku

又例如,在 hello.txt 文件相同的目录下, 创建一个名为 sonnet29.txt 的文件, 并在其中写入以下文本:

When, in disgrace with fortune and men's eyes ,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my fate,

确保用换行分开这4行。 然后在交互式环境中输入以下代码:

>>> sonnetFile = open('sonnet29.txt')
>>> sonnetFile.readlines()
["When, in disgrace with fortune and men's eyes ,n",
 'I all alone beweep my outcast state,n',
 'And trouble deaf heaven with my bootless cries, n',
 'And look upon myself and curse my fate,']

请注意,每个字符串值都以一个换行字符 \n 结束。 除了文件的最后一行。与单个大字符串相比, 字符串的列表通常更容易处理。

2.2.3. 写入文件

Python 允许你将内容写入文件, 方式与 print() 函数将字符串“写”到屏幕上类似。 但是,如果打开文件时用读模式,就不能写入文件。 你需要以“写入纯文本模式”或“添加纯文本模式” 打开该文件,或简称为“写模式”和“添加模式”。

写模式将覆写原有的文件,从头开始,就像你用一个新值覆写一个变量的值。 将 'w' 作为第二个参数传递给 open() ,以写模式打开该文件。 不同的是,添加模式将在已有文件的末尾添加文本。 你可以认为这类似向一个变量中的列表添加内容, 而不是完全覆写该变量。将 'a' 作为第二个参数传递给 open(),以添加模式打开该文件。

如果传递给 open() 的文件名不存在, 写模式和添加模式都会创建一个新的空文件。 在读取或写入文件后, 调用 close() 方法,然后才能再次打开该文件。

让我们整合这些概念。在交互式环境中输入以下代码:

>>> baconFile = open('bacon.txt','w')
>>> baconFile.write('Hello world!\n')
13
>>> baconFile.close()
>>> baconFile = open('bacon.txt', 'a')
>>> baconFile.write('Bacon is not a vegetable.')
25
>>> baconFile.close()
>>> baconFile = open('bacon.txt')
>>> content = baconFile.read()
>>> baconFile.close()
>>> print(content)
Hello world!
Bacon is not a vegetable.

首先,我们以写模式打开 bacon.txt 。 因为还没有 bacon.txt ,Python 就创建了一个。 在打开的文件上调用 write() , 并向 write() 传入字符串参数 'Hello world! \n', 将字符串写入文件,并返回写入的字符个数, 包括换行符。然后关闭该文件。

为了将文本添加到文件已有的内容, 而不是取代我们刚刚写入的字符串, 我们就以添加模式打开该文件。 向该文件写入'Bacon is not a vegetable.', 并关闭它。最后,为了将文件的内容打印到屏幕上, 我们以默认的读模式打开该文件,调用 read(), 将得到的内容保存在 content 中, 关闭该文件,并打印 content

请注意, write() 方法不会像 print()函数那样, 在字符串的末尾自动添加换行字符。必须自己添加该字符。

在 Python 中,读写文件有3个步骤:

  1. 调用 open() 函数,返回一个File对象。

  • 调用File对象的 read()write() 方法。

  • 别忘了调用方法close将文件关闭。

通常,程序退出时将自动关闭文件对象(也可能在退出程序前这样做), 因此是否将读取的文件关闭并不那么重要。 然而,关闭文件没有坏处,在有些操作系统和设置中,还可避免无意义地锁定文件以防修改。 另外,这样做还可避免用完系统可能指定的文件打开配额。

对于写入过的文件,一定要将其关闭,因为Python可能缓冲你写入的数据(将数据暂时存储 在某个地方,以提高效率)。 因此如果程序因某种原因崩溃,数据可能根本不会写入到文件中。 安全的做法是,使用完文件后就将其关闭。 如果要重置缓冲,让所做的修改反映到磁盘文件中, 但又不想关闭文件,可使用方法flush。 然而,需要注意的是,根据使用的操作系统和设置,flush 可能出于锁定考虑而禁止其他正在运行的程序访问这个文件。 只要能够方便地关闭文件,就应将 其关闭。

要确保文件得以关闭,可使用一条 try/finally 语句,并在 finally 子句中调用 close

# 在这里打开文件

try:
    # 将数据写入到文件中

finally:
    file.close()

实际上,有一条专门为此设计的语句,那就是with语句。

with open("somefile.txt") as somefile:
    do_something(somefile)

with 语句让你能够打开文件并将其赋给一个变量(这里是somefile)。 在语句体中,你将数据 写入文件(还可能做其他事情)。到达该语句末尾时,将自动关闭文件,即便出现异常亦如此。

with 语句实际上是一个非常通用的结构,允许你使用所谓的上下文管理器。 上下文管理器是支持两个方法的对象: enterexit

方法 enter 不接受任何参数,在进入 with 语句时被调用,其返回值被赋给关键字 as 后面的变量。

方法 exit 接受三个参数:异常类型、异常对象和异常跟踪。 它在离开方法时被调用。如果 exit 返回 False ,将抑制所有的异常。 文件也可用作上下文管理器。它们的方法 enter 返回文件对象本身,而方法 exit 关闭文件。

2.2.4. 使用管道重定向输出

在 Bash 等 Shell 中,可依次输入多个命令,并使用管道将它们链接起来,如下所示:

$ cat somefile.txt | python somescript.py | sort

这条管道线包含三个命令。

  1. cat somefile.txt:将文件somefile.txt的内容写入到标准输出(sys.stdout)。

  • python somescript.py:执行Python脚本somescript。这个脚本从其标准输入中读取,并 将结果写入到标准输出。

  • sort:读取标准输入(sys.stdin)中的所有文本,将各行按字母顺序排序,并将结果写 入到标准输出。

但这些管道字符( | )有何作用呢? 脚本 somescript.py 的作用是什么呢? 管道将一个命令的 标准输出链接到下一个命令的标准输入。 很聪明吧? 因此可以认为, somescript.py 从其 sys.stdin 中读取数据(这些数据是somefile.txt写入的), 并将结果写入到其 sys.stdout(sort 将从这里获取数据)。

代码清单11-1是一个使用sys.stdin的简单脚本(somescript.py)。代码清单11-2显示了文件somefile.txt的内容。

代码清单: 计算sys.stdin中包含多少个单词的简单脚本

>>> # somescript.py
>>> import sys
>>> text = sys.stdin.read()
>>> words = text.split()
>>> wordcount = len(words)
>>> print('Wordcount:', wordcount)
Wordcount: 0

代码清单11 -2 一个内容荒谬的文本文件

Your mother was a hamster and your
father smelled of elderberries.
cat somefile.txt | python somescript.py 的结果如下:
Wordcount: 11

随机存取

在本章中,我将文件都视为流,只能按顺序从头到尾读取。 实际上,可在文件中移动, 只访问感兴趣的部分(称为随机存取)。 为此,可使用文件对象的两个方法:seek()tell()

方法 seek(offset[, whence]) 将当前位置(执行读取或写入的位置)移到 offsetwhence 指定的地方。 参数 offset 指定了字节(字符)数, 而参数 whence 默认为 io.SEEK_SET(0), 这意味着偏移量是相对于文件开头的(偏移量不能为负数)。 参数 whence 还可设置为 io.SEEK_CUR(1)io.SEEK_END(2) , 其中前者表示相对于当前位置进行移动(偏移量可以为负), 而后者表示相对于文件末尾进行移动。请看下面的示例:

>>> thefile = 'somefile.txt'
>>> f = open( thefile , 'w')
>>> f.write('01234567890123456789')
>>> f.seek(5)
5
>>> f.write('Hello, World!')
13
>>> f.close()
>>> f = open( thefile )
>>> f.read()
'01234Hello, World!89'

方法 tell()返回当前位于文件的什么位置,如下例所示:

>>> f = open( thefile )
>>> f.read(3)
>>> f.read(2)
'34'
>>> f.tell()
5