用户: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,文件流会这样读取文件信息:

  1. 指针 0026700A02 读取到 90 并发送给 Python。
  2. 指针变为 0026700A03
  3. 指针 0026700A03 读取到 4E 并发送给 Python。
  4. ...

从而,可以借助文件流读取到文件内容 90 4E 54 46 53 20 20 20 ...

写入文件的方式

编辑

写入文件同样是通过指针进行的。

依旧按上述例子,假如我们输入的路径转换为指针 0026700A70,并准备写入 00 00 00 00 00 00 ... 文件流会这样写入信息:

  1. 指针 0026700A70 被写入 00
  2. 指针变为 0026700A71
  3. 指针 0026700A71 被写入 00
  4. ...

从而,可以借助文件流写入内容。

因为写入和读取都会改变指针,一个流要么写入文件、要么读取文件。它不可能自动地先读取一部分内容,再从部分开头开始覆写;它也无法自动重新读取已经读取过的内容——因为流只包含一个指针。如果想要进行这些操作,程序员应该自己保留一份指针的复制(因为文件系统会自己转换,所以保留文件路径即可)。

缓冲区

编辑

在实际实现时,对硬盘内容进行零星的读取和写入都是相当耗时的。为此,文件流会带有“缓冲区”。缓冲区位于内存中,从而读取和写入消耗的时间较少。

不支持缓冲区的流也被称作是“原始流”。

读取缓冲区

编辑

我们依旧使用上面表格的例子:

指针为 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 的流有一些特殊的功能。在 Python 文档中,用 file object 或 file-like object 来称呼这种对象。

文本流和字节流

编辑

Python 的流被分为文本流和字节流两种。字节流和上一章节的示例是一致的。

文本流的原理和字节流是一致的——不过其读取的单位从字节变成字符。比如:对于读取 GBK 编码的文本文件(GBK编码中,一个字符要两个字节),文本流会一次性读两个字节、转换为字符、再更改指针。

如果使用 Windows 系统,Python 的文本流在读取字符时,会把 Windows 平台的换行符 \r\n 转换为 \n 进行读取。而在写入字符时又会把 \n 转换为 \r\n 进行写入。因此,在存储字节时,哪怕正确地将字节转换成字符、并用文本流的方式写入,也可能会由于自动转换而出错。读者不应该用文本流来处理比如 jpg 或 exe 等二进制文件的数据。

创建文件流

编辑
关于 mode 的测试
这个 页面 展示了对其中一些模式的测试、以及测试结果。

我们可以用 open 函数创建文件流。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

其中,file 是要打开文件的路径

mode 设定文件流的模式。

buffering 为缓冲策略。

encodingerrors 应当仅在创建文本流时输入,这两个参数和字符的编码方式有关,具体参见 字符串编解码 。如果创建文本流,则 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 为 None,则写入的任何 '\n' 字符都将转换为系统的默认行分隔符 os.linesep
    • 如果 newline 是空字符串或 '\n',则不进行翻译。
    • 如果 newline 是'\r''\r\n',则写入的 '\n' 字符将被转换为 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
因为指针一定在文件末尾,读取时,不会读取到任何
内容,也不会移动指针。

buffering

编辑
buffering
数值 模式
-1 使用默认缓冲策略
0 关闭缓冲(仅字节流可用,此时创建的是原始流)
1 行缓冲(仅文本流、写入时可用)
大于1
的整数
设置固定字节数的缓冲
(仅 'r+' 以外的模式可用)

buffering 参数用于设置缓冲策略。此参数应当传入一个整数。

默认缓冲策略为:

  • 字节流和一般的文本流使用固定大小的缓冲区,大小取决于设备、一般为 4096 B 或 8192 B。
  • “交互式”文本流使用行缓冲。交互式文本流的 isatty() 方法会返回 True

文件流的实例方法与属性

编辑

以下内容讲解了文件流的常用操作和属性。所有的操作和属性都可以在 Python io 模块的文档中,“类的层次结构”章节的几个小结 中查询到。

关闭

编辑

closed 如果流已经被关闭,则该属性为 True

close() 此方法会情况读取缓冲区、把写入缓冲区的内容写入到硬盘上,并关闭流。如果流已经处于关闭状态,此方法会什么都不做。被关闭的流无法再执行任何读写操作。如果在使用文件流时,未使用此方法就退出 Python 可能会导致出错。比如内容没有被实际写入等。

为防止忘记关闭,可以用 with 语句来创建文件流。具体参见下面的章节。

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

参考文献

编辑