用户:Xyy23330121/Python/文件处理
在使用 Python 时,有时我们希望 Python 自动帮助处理一些文件。本页面将讲述如何用 Python 处理基本的文本文档,以及如何进行扩展。
“文件流”是处理文件的基本工具。
为了更好地理解文件流如何操作文件,我们应当先对其原理有基本的了解:“流”就是指针。以下内容简化了流的原理。
如果读者学习过 C 语言,对指针一定很熟悉。简单来讲,指针是指向内存位置的内容。以下是一部分磁盘信息的示例表格:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0026700A0* | EB | 52 | 90 | 4E | 54 | 46 | 53 | 20 | 20 | 20 | 20 | 00 | 02 | 08 | 00 | 00 |
0026700A1* | 00 | 00 | 00 | 00 | 00 | F8 | 00 | 00 | 3F | 00 | FF | 00 | 00 | 38 | 13 | 00 |
0026700A2* | 00 | 00 | 00 | 00 | 80 | 00 | 80 | 00 | E5 | EB | BB | 1D | 00 | 00 | 00 | 00 |
0026700A3* | 00 | 00 | 0C | 00 | 00 | 00 | 00 | 00 | 02 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
0026700A4* | F6 | 00 | 00 | 00 | 01 | 00 | 00 | 00 | 5B | A2 | 8E | 1C | C7 | 8E | 1C | D6 |
0026700A5* | 00 | 00 | 00 | 00 | FA | 33 | C0 | 8E | D0 | BC | 00 | 7C | FB | 68 | C0 | 07 |
0026700A6* | 1F | 1E | 68 | 66 | 00 | CB | 88 | 16 | 0E | 00 | 66 | 81 | 3E | 03 | 00 | 4E |
0026700A7* | 54 | 46 | 53 | 75 | 15 | B4 | 41 | BB | AA | 55 | CD | 13 | 72 | 0C | 81 | FB |
指针就是确定计算机应该读取哪部分字节的。按上述例子,如果指针为 0026700A02
,则计算机会读取到 90
。
在具体使用时,我们一般都使用文件路径。这是因为文件系统会自动将路径转换为指针,从而读取正确的文件信息。
文件流通过改变自身的指针来读取文件。
依旧按上述例子,假如我们输入的路径转换为指针 0026700A02
,文件流会这样读取文件信息:
- 指针
0026700A02
读取到90
并发送给 Python。 - 指针变为
0026700A03
。 - 指针
0026700A03
读取到4E
并发送给 Python。 - ...
从而,可以借助文件流读取到文件内容 90 4E 54 46 53 20 20 20 ...
。
写入文件同样是通过指针进行的。
依旧按上述例子,假如我们输入的路径转换为指针 0026700A70
,并准备写入 00 00 00 00 00 00 ...
文件流会这样写入信息:
- 指针
0026700A70
被写入00
。 - 指针变为
0026700A71
。 - 指针
0026700A71
被写入00
。 - ...
从而,可以借助文件流写入内容。
因为写入和读取都会改变指针,一个流要么写入文件、要么读取文件。它不可能自动地先读取一部分内容,再从部分开头开始覆写;它也无法自动重新读取已经读取过的内容——因为流只包含一个指针。如果想要进行这些操作,程序员应该自己保留一份指针的复制(因为文件系统会自己转换,所以保留文件路径即可)。
在实际实现时,对硬盘内容进行零星的读取和写入都是相当耗时的。为此,文件流会带有“缓冲区”。缓冲区位于内存中,从而读取和写入消耗的时间较少。
不支持缓冲区的流也被称作是“原始流”。
我们依旧使用上面表格的例子:
指针为 0026700A00
,缓冲大小设置为 16 B (一般会更大)。流会事先读取 16 B 的内容,即 0026700A00
~ 0026700A0F
的内容,并存储到内存中。
内存中的情况 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0000 | EB | 52 | 90 | 4E | 54 | 46 | 53 | 20 | 20 | 20 | 20 | 00 | 02 | 08 | 00 | 00 |
在使用时,流会先在内存中读取。为此,需要一个内存指针。如果内存中的内容读取完了,就释放缓冲区,指针变为 0026700A10
,并重新缓冲。
这样,流就将零星的对磁盘的读取,变成了大块的对磁盘的读取以及零星的对内存的读取。显著提高了读取速度。
写入缓冲区与读取缓冲区类似,它同样是将小块需写入的信息在内存中整合成大块,再一次性写入磁盘的工具。
Python 的流有一些特殊的功能。在 Python 文档中,用 file object 或 file-like object 来称呼这种对象。
Python 的流被分为文本流和字节流两种。字节流和上一章节的示例是一致的。
文本流的原理和字节流是一致的——不过其读取的单位从字节变成字符。比如:对于读取 GBK 编码的文本文件(GBK编码中,一个字符要两个字节),文本流会一次性读两个字节、转换为字符、再更改指针。
如果使用 Windows 系统,Python 的文本流在读取字符时,会把 Windows 平台的换行符 \r\n
转换为 \n
进行读取。而在写入字符时又会把 \n
转换为 \r\n
进行写入。因此,在存储字节时,哪怕正确地将字节转换成字符、并用文本流的方式写入,也可能会由于自动转换而出错。读者不应该用文本流来处理比如 jpg 或 exe 等二进制文件的数据。
我们可以用 open 函数创建文件流。
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
其中,file 是要打开文件的路径。
mode 设定文件流的模式。
buffering 为缓冲策略。
encoding 和 errors 应当仅在创建文本流时输入,这两个参数和字符的编码方式有关,具体参见 字符串编解码 。如果创建文本流,则 encoding 默认为 locale.getencoding()
[1]的结果,而 errors 为 'strict'
。
newline 也仅在文本流中有用,它决定如何解析来自流的换行符。
- 读取时
- 如果 newline 为
None
,则把'\n'
,'\r'
或'\r\n'
视为换行符,并翻译成'\n'
。 - 如果 newline 为空字符串,则把
'\n'
,'\r'
或'\r\n'
视为换行符,但不进行翻译。 - 如果 newline 为
'\n'
,'\r'
或'\r\n'
,则仅把 newline 视为换行符。
- 如果 newline 为
- 写入时
- 如果 newline 为
None
,则写入的任何'\n'
字符都将转换为系统的默认行分隔符os.linesep
。 - 如果 newline 是空字符串或
'\n'
,则不进行翻译。 - 如果 newline 是
'\r'
或'\r\n'
,则写入的'\n'
字符将被转换为 newline。
- 如果 newline 为
closefd 以及 opener 和文件名描述符有关,在一些系统上不适用,因此这里不多赘述。
以下表格列出了所有可用的模式,以及其使用时的表现。
文本流 模式 |
字节流 模式 |
支持的 操作 |
指针位置 | 备注 |
---|---|---|---|---|
'r' | 'rb' | 读 | 文件开头 | 和“流”读取文件章节的表现完全一致。 |
'w' | 'wb' | 写 | 开头末尾 | 打开时会清空文件内容,所以指针位置在开头、也在 末尾。 |
'a' | 'ab' | 读写 | 文件末尾 | 创建文件。 如果文件已存在,则会把指针放在文件末尾。 |
'x' | 'xb' | 写 | 开头末尾 | 创建文件。 如果文件已存在,则会报错 oserror 。
|
'r+' | 'r+b' | 读写 | 文件开头 | 打开时不会清空文件内容。 无论读取还是写入都会移动指针。 写入时,直接对对应位置的内容进行替换。 |
'w+' | 'w+b' | 读写 | 开头末尾 | 打开时会清空文件内容。 因为指针一定在文件末尾,读取时,不会读取到任何 内容,也不会移动指针。 |
'a+' | 'a+b' | 读写 | 文件末尾 | 创建文件。 如果文件已存在,则会把指针放在文件末尾。 因为指针一定在文件末尾,读取时,不会读取到任何 内容,也不会移动指针。 |
'x+' | 'x+b' | 读写 | 开头末尾 | 创建文件。 如果文件已存在,则会报错 oserror 。因为指针一定在文件末尾,读取时,不会读取到任何 内容,也不会移动指针。 |
数值 | 模式 |
---|---|
-1 | 使用默认缓冲策略 |
0 | 关闭缓冲(仅字节流可用,此时创建的是原始流) |
1 | 行缓冲(仅文本流、写入时可用) |
大于1 的整数 |
设置固定字节数的缓冲 (仅 'r+' 以外的模式可用)
|
buffering 参数用于设置缓冲策略。此参数应当传入一个整数。
默认缓冲策略为:
- 字节流和一般的文本流使用固定大小的缓冲区,大小取决于设备、一般为 4096 B 或 8192 B。
- “交互式”文本流使用行缓冲。交互式文本流的
isatty()
方法会返回True
。
以下内容讲解了文件流的常用操作和属性。所有的操作和属性都可以在 Python io 模块的文档中,“类的层次结构”章节的几个小结 中查询到。
closed 如果流已经被关闭,则该属性为 True
。
close() 此方法会情况读取缓冲区、把写入缓冲区的内容写入到硬盘上,并关闭流。如果流已经处于关闭状态,此方法会什么都不做。被关闭的流无法再执行任何读写操作。如果在使用文件流时,未使用此方法就退出 Python 可能会导致出错。比如内容没有被实际写入等。
为防止忘记关闭,可以用 with 语句来创建文件流。具体参见下面的章节。
Python 的文件流本身也是一个上下文管理器。其 __enter__ 方法会返回其自身,而 __exit__ 方法会调用 close() 方法。于是,可以这样创建文件流并使用:
with open(file) as text:
print(text.read())
read(size=-1, /)
- 对于字节流,从对象中读取 size 个字节并返回。如果 size 为 -1,则读取所有字节,直到文件结束。
- 对于文本流,从对象中读取 size 个字符并返回。如果 size 为 -1,则读取所有字符,直到文件结束。
readinto(b, /)
- 对于字节流,该项会把字节数据写入 bytearray 对象 b 中,并返回读取的字节数。
readline(size=-1, /) 读取一行的内容。如果指定了 size ,则最多读取 size 个字节。
对于字节流而言,行结束符为 b'\n'
;对于文本流而言,则按照创建文件流时使用的 newline
参数来决定。
readlines(hint=-1, /) 读取内容,并按行分割为列表。列表中的元素是读取到的每一行的内容(包含换行符)。如果读取完一行时,读取的总字符数(文本流) / 字节数(字节流)超出 hint
,则不会继续读取下一行。特别的,对文件流进行迭代操作 for line in file: ...
时,等同于使用 for line in file.readlines(): ...
。
readable() 如果流可以读取,则该方法会返回 True
。
write(b, /)
- 对于字节流,将给定的 bytes 或 bytearray 写入到文件中,并返回写入的字节数。
- 对于文本流,将给定的字符串写入到文件中,并返回写入的字符数。
writelines(lines, /) 将列表lines
中的内容写入到流中,不会添加行分隔符。
writeable() 如果流可以写入,则该方法会返回 True
。
flush() 把写入缓存区中所有内容写到磁盘上。这对于只读的流不起作用。
seek(offset, whence=os.SEEK_SET, /) 将指针修改到对应的字节。其中offset
为相对于 whence
的字节数,而 whence
可以为以下值:
数值 | os 模块中的常量名 | 注释 |
---|---|---|
0 | os.SEEK_SET | 文件开头位置(默认) |
1 | os.SEEK_CUR | 当前指针位置 |
2 | os.SEEK_END | 文件末尾 |
某些操作系统还支持其他的值。
tell() 返回当前指针位置。
seekable() 如果流支持上述指针操作,返回 True
。