User: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 和文件名描述符有关,在一些系统上不适用,因此这里不多赘述。

mode[编辑 | 编辑源代码]

以下表格列出了所有可用的模式,以及其使用时的表现。

文本流
模式
字节流
模式
支持的
操作
指针位置 备注
'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

參考文獻[编辑 | 编辑源代码]