# 文件读写
# open()
用户读写一个文件时,基本流程如下:
- 调用 open() 函数来打开文件,它会返回一个文件对象。
- 调用文件对象的方法,比如 read() 读取数据、 write() 写入数据。
- 调用文件对象的 close() 方法来关闭文件。
open() 是 Python 的内置函数,用于打开磁盘中的一个文件。形参如下:
open( file, # 指定要打开的文件路径,或者一个 int 型的文件描述符 mode = 'r', # 打开文件的模式 buffering = -1, # 缓冲策略 encoding = None, # 编码格式,仅在文本模式可用 errors = None, # 处理编码、解码错误的策略,仅在文本模式可用。取值为 None 时相当于 'strict' ,会抛出异常 newline = None, # 换行符,仅在文本模式可用 closefd = True, # 输入文件描述符打开文件时,调用 close() 方法是否关闭该文件描述符 ...)
进程每打开一个文件,会被操作系统自动分配一个文件描述符,取值为 int 类型,相当于文件编号。
- 在 Windows 系统中,如果一个文件已经被某个进程打开了,则不允许该文件被其它进程修改,直到前一个进程释放文件描述符。
# 形参
# mode
open() 函数中,mode 参数表示对文件的访问模式,有以下几种取值:
r
- :read ,只读模式。
- 如果文件不存在,则报错:
FileNotFoundError: No such file or directory
w
- :write ,只写模式。
- 如果文件不存在,则自动创建它,再进行写入。
- 如果文件已存在,则将长度截断为 0 ,再进行写入。这样属于覆盖式写入。
a
- :append ,追加写模式。
- 如果文件不存在,则自动创建它,再进行写入。
- 如果文件已存在,则到文件尾部进行追加写入。
x
- :create ,先创建文件,再以只写模式打开。
- 如果文件已存在,则报错:
FileExistsError: File exists
+
- :可读可写模式。
- 必须与 r、w、a、x 模式其中之一组合使用,例如
r+
、w+
。 r+
模式支持覆盖式写入,比如通过 seek() 和 write() 修改文件中的部分内容。
mode 参数中,还可以指定文件流的类型,有两种取值:
b
- :binary ,二进制模式,读写的值是 bytes 类型。
- 此时文件对象绑定的 IO 对象为二进制流,又称为字节流。
- 所有类型的文件都是以二进制格式存储的,因此可以按二进制格式直接读写。不过通常将二进制值转换成字节,便于阅读。
t
- :text ,文本模式,读写的值是 str 类型。
- 此时文件对象绑定的 IO 对象为文本流,又称为字符流。
- 与二进制模式相比,文本模式更方便人类阅读,但只适合打开文本格式的文件,不适合打开图片、音频等特殊编码的文件。
- 读取文件时,需要先读取文件中存储的二进制数据,再按某种编码格式转换成 str 类型。
- 写入文件时,需要先将写入的 str 类型的值,按某种编码格式转换成 bytes 类型,再写入文件。
用户调用 open() 函数时,mode 参数的默认值为
rt
。- 可以组合使用多种模式,例如
rt
、rb
、rb+
。 - t、b 两种模式,只能二选一。
- 可以组合使用多种模式,例如
# buffering
open() 读写文件时,默认启用了缓冲,但没有启用缓存。
缓冲(buffer)
- 原理:
- 调用
f.write()
写入一段数据到磁盘文件时,该方法会立即执行完毕,但数据不一定立即写入磁盘文件。 - 这些数据会先放在内存中的缓冲区,等累积了一定数量 bytes 之后,才写入磁盘。
- 调用
- 优点:
f.write()
方法能立即执行完毕,不必等待磁盘 IO 完成。- 减少了磁盘 IO 的次数。否则每写入 1 byte 就进行一次磁盘 IO ,开销大,效率低。
- 缺点:
- 如果进程突然挂掉,缓冲区中的数据,可能来不及保存到磁盘,从而丢失。
- 例:
- 调用
f.flush()
方法,会将刷新缓冲区,将其中等待写入磁盘的数据,立即写入磁盘。 - 调用
f.close()
方法时,会自动调用f.flush()
方法。
- 调用
- 原理:
缓存(cache)
- 原理:
- 调用
f.read()
从磁盘文件读取一段数据到内存时,即使处理完这段数据,这段数据也会在内存中缓存一段时间,方便用户未来再次读取。
- 调用
- 优点:
- 方便重复读取数据。
- 缺点:
- 会增加 Python 程序占用的内存空间。
- 例:
- 可以执行
io.BytesIO()
,在内存中创建一个缓存区。
- 可以执行
- 原理:
open() 函数中,buffering 参数表示缓冲策略,可取值:
0
:取消缓冲,仅在二进制模式可用。1
:采用行缓冲,仅在文本模式可用。n
> 0 :采用 n 字节大小的缓冲区。- 默认值为
-1
,表示自动选择缓冲策略,- 如果文件为 tty 类型,则采用行缓冲。
- 如果文件为其它类型,则采用系统默认大小的缓冲区。这取决于
io.DEFAULT_BUFFER_SIZE
,通常为 8 KB 。
# encoding
open() 函数中, encoding 参数表示文件内容的编码格式。
- 如果不指定 encoding 参数,则 Python 会采用当前计算机的默认编码格式。
- 可执行
locale.getpreferredencoding(False)
,查看当前的编码格式。 - 在 Linux 系统上,它通常是 'utf-8' 。
- 在 Windows 中文版系统上,它通常是 'cp936' 。
- 可执行
- 如果不指定 encoding 参数,则 Python 会采用当前计算机的默认编码格式。
同一个计算机中,
- 不同文件的内容,可能采用不同的编码格式,例如 'utf-8'、'gbk' 。
- 调用 open() 打开一个文件时,用户需要指定正确的 encoding 参数,否则不能解读文件的内容。
- 不同文件的文件名,通常统一采用 'utf-8' 编码格式。
- 可执行
sys.getfilesystemencoding()
,查看当前的编码格式。 - 调用 open() 打开一个文件时,会自动处理文件名的编码格式,不需要用户干预。
- 可执行
- 不同文件的内容,可能采用不同的编码格式,例如 'utf-8'、'gbk' 。
# newline
open() 函数中,newline 参数表示换行符,用于自动划分每行文本。
- 例如类 Unix 系统的 newline 默认是
\n
,Windows 系统的 newline 默认是\r\n
。
- 例如类 Unix 系统的 newline 默认是
newline 参数可取值:
- 默认为
newline=None
:- 读取文件时,会将文件中的
'\r'
、'\n'
、'\r\n'
都识别为换行符,并且统一转换成'\n'
,然后返回。 - 写入文件时,会将待写文本中的
'\n'
都替换成当前平台的默认换行符(取决于os.linesep
),再写入文件。
- 读取文件时,会将文件中的
- 如果指定了
newline=''
:- 读取文件时,会将文件中的
'\r'
、'\n'
、'\r\n'
都识别为换行符,然后返回该行文本。 - 写入文件时,会直接写入。
- 读取文件时,会将文件中的
- 如果指定了 newline 等于其它值:
- 读取文件时,只会识别指定的换行符,然后返回该行文本。
- 写入文件时,会将待写文本中的
'\n'
都替换成指定的换行符,再写入文件。
- 默认为
# 属性
文件对象的常用属性:
>>> f.name # 文件名 '1.txt' >>> f.mode # 打开文件的模式 'w' >>> f.fileno() # 文件描述符 3
以文本模式打开文件时,可访问以下属性:
>>> f.encoding 'cp936' >>> f.errors 'strict' >>> f.line_buffering False >>> f.newlines # 这里 newlines 为 None >>> f.buffer # 每个文件会绑定一个 BinaryIO 对象,用于存储文件的二进制内容 <_io.BufferedWriter name='1.txt'> >>> f.buffer.mode # 以文本模式打开文件时,实际上是先用二进制模式打开文件,再转换成文本模式 'wb'
# 方法
文件对象的常用方法:
.close() -> None # 关闭文件 .closed() -> bool # 判断文件是否已关闭 .fileno() -> int # 返回文件描述符 .flush() -> None # 刷新缓冲区 .isatty() -> bool # 判断 IO 对象的输入、输出是否指向终端设备 .readable() -> bool # 判断是否可读 .read(n: int = -1) -> AnyStr # 读取 n 个字节的数据,然后返回。默认 n 为 -1 ,即无限制 .readline(limit: int = -1) -> AnyStr # 读取一行,最多读取 limit 个字节,然后返回(包括换行符)。默认 limit 为 -1 ,即无限制 .readlines(hint: int = -1) -> List[AnyStr] # 读取文件,最多读取 hint 行,然后返回一个包含各行的列表(包括换行符)。默认 hint 为 -1 ,无 f.限制 .writable() -> bool # 判断是否可写 .write(s: AnyStr) -> int # 写入 s 的值,然后返回写入的字节数 .writelines(lines: Sequence[AnyStr]) -> None # 写入多行,需要输入一个包含各行的序列(包括换行符),例如 f.writelines(['Hello\n','world\n'])
- 上述的 AnyStr 类型是指 str 或 bytes 等类型。
- 以文本模式打开文件时,读写的数据必须为 Str 类型。
- 以二进制模式打开文件时,读写的数据必须为 bytes 或 bytearray 类型。
- 读取大文件时,直接调用 read() 会占用太多内存,应该改用 read(1000) 等方式。
- 上述的 AnyStr 类型是指 str 或 bytes 等类型。
读写文件的示例:
>>> f = open('1.txt', 'w') >>> type(f) <class '_io.TextIOWrapper'> # 文件对象是对 IO 对象的封装 >>> f.read() # 读取文件,这里因为不能读取而报错 io.UnsupportedOperation: not readable >>> f.write('Hello') # 写入文件 5 >>> f.close() # 关闭文件
读取文件的示例:
>>> f = open('1.txt', 'w+') >>> f.write('Hello\n') 6 >>> f.seek(0) # 将文件指针移到文件首部 0 >>> f.read() # 读取文件的全部内容 'Hello\n' >>> f.seek(0) 0 >>> f.readline() # 读取一行 'Hello\n' >>> f.seek(0) 0 >>> f.readlines() # 读取全部行 ['Hello\n'] >>> f.seek(0) 0 >>> [line for line in f] # 文件对象支持遍历,每次遍历一行,相当于 f.readline() ['Hello\n'] >>> f.close()
# close()
- 用户操作完文件之后,应该调用 close() 方法来关闭文件。
- 关闭文件时,会释放其缓冲区,避免内存泄漏。还会释放文件描述符。
- 文件被关闭之后,不能再进行读写,否则会报错:
ValueError: I/O operation on closed file
- 可用关键字 with 打开文件,进行上下文管理。
- 例:
with open('1.txt') as f: f.read()
- 当 Python 解释器执行完 with 语句块或者抛出异常时,会自动调用 close() 方法完成清理。因此上例相当于:
try: f = open('1.txt') f.read() finally: f.close()
- 关键字 with 支持同时打开多个上下文:
with open('1.txt') as f1, open('2.txt') as f2: pass
- 例:
# seek()
- 读写文件时,Python 会通过文件指针,记录当前读写到文件中第几个字节。
- 使用 r、w 模式打开文件时,指针最初指向第 0 个字节。
- 每次读、写 n 个字节,Python 就会自动将指针的位置向后移动 n 个字节。
- 使用 a 模式打开文件时,每次读、写操作之前,都会自动将指针指向最后一个字节。
- 可以调用 f.seek() ,但每次读、写操作之前依然会将指针指向最后一个字节。
- 使用 r、w 模式打开文件时,指针最初指向第 0 个字节。
- 相关方法:
.tell() -> int # 返回文件指针当前的位置 .seekable() -> bool # 判断是否可以移动文件指针的位置,取决于 IO 对象是否支持随机位置访问 .seek(offset: int, whence: int = 0) -> int # 改变文件指针的位置,然后返回文件指针当前的位置
- offset :表示指针从 wherece 开始的偏移量,即第几个字节。可以为负。
- whence :可选参数,表示移动指针的起始位置。可取值:
0
:文件首部,即第 0 个字节处。1
:指针当前位置,即 f.tell() 的值。2
:文件尾部,最最后一个字节处。
- 例:
>>> f.read() 'Hello' >>> f.read() # 当文件指针指向文件尾部时,如果继续读取,则返回的内容为空 '' >>> f.tell() 5 >>> f.seek(0) 0 >>> f.read() 'Hello'
# truncate()
- 用 truncate() 方法可以截断文件:
.truncate(size: int = None) -> int # 将文件大小调整为 size ,然后返回调整后的大小
- 截断文件时,需要允许写入,即 f.writeable() == True 。
- 如果不指定 size ,则会采用文件指针当前的位置,即 f.tell() 的值。
- 如果指定的 size 比文件长度小,则会截断文件,删除文件尾部多余的字节。
- 如果指定的 size 比文件长度大,则会扩展文件,在文件尾部填充一些 Null 字节。
- 例:
>>> f = open('1.txt', 'w+') >>> f.write('Hello') 5 >>> f.truncate(0) 0 >>> f.tell() # 调用 truncate() 方法时,不会自动移动文件指针 5 >>> f.write('Hello') # 此时文件长度已经截断为 0 ,但指针依然指向第 5 个字节,如果继续写入则会在前面填上 4 个 Null 字节,产生稀疏文件 5 >>> f.seek(0) 0 >>> f.read() '\x00\x00\x00\x00\x00Hello' >>> f.close()
- 例:清空文件之后重新写入
with open('1.txt', 'w+') as f: f.read() f.seek(0) f.truncate() f.write('Hello')
# import io
:Python 的标准库,用于进行 IO 操作。
# 原理
- 进行 IO 操作的对象,称为 IO 对象、流对象。
- io 库中,常用的几个类:
io.BytesIO # 用作字节流(又称为二进制流),只能写入 bytes 类型的值 io.StringIO # 用作字符流,只能写入 str 类型的值
- 用 open() 函数打开文件时,会返回一个文件对象,实际上是对
io.BytesIO
或io.StringIO
的封装。因此它们的操作方法,基本一致。
- 用 open() 函数打开文件时,会返回一个文件对象,实际上是对
# 用法
用法示例:
>>> import io >>> s = io.StringIO('Hello\n') # 创建 IO 对象,并初始化其内容 >>> s.write('World') # 创建 IO 对象之后,指针最初指向第 0 个字节处,此时写入可能覆盖原有数据 5 >>> s.read() # 当前指针倒数第二个字节处,因此只会读取到 '\n' '\n' >>> s.seek(0) # 将指针指向 IO 对象的首部 0 >>> s.read() 'Hello\n' >>> s.getvalue() # 获取 IO 对象的全部内容,该方法不受指针位置的影响 'Hello\n' >>> s.truncate(3) # 截断 IO 对象 3 >>> s.getvalue() 'Wor' >>> s.close()
操作完 IO 对象之后,应该调用 close() 方法来关闭它。或者通过 with 关键字创建 IO 对象,会自动关闭它。
with io.StringIO() as s: ...
例:读取磁盘文件的内容,缓存到 BytesIO 中,也就是手动创建一个缓存区
with io.BytesIO() as buf: with open('1.jpg', 'rb') as f: buf.write(f.read()) ...