# 文件对象
# 打开文件
Python 中可以调用 open() 函数来打开文件,返回一个文件对象。函数定义如下:
open(
file, # 指定要打开的文件路径,或者一个 int 型的文件描述符
mode = 'r', # 打开文件的模式
buffering = -1, # 缓冲策略
encoding = None, # 编码格式,仅在文本模式可用
errors = None, # 处理编码、解码错误的策略,仅在文本模式可用。取值为 None 时相当于 'strict' ,会抛出异常
newline = None, # 换行符,仅在文本模式可用
closefd = True, # 输入文件描述符打开文件时,调用 close() 方法是否关闭该文件描述符
...)
- encoding 参数可以取值为 'utf-8'、'gbk' 等。
- 如果不指定 encoding 参数,Python 会采用当前平台的默认编码格式,取决于
locale.getpreferredencoding(False)
。- 在 Linux 系统上,它通常是 'utf-8' 。
- 在 Windows 中文版系统上,它通常是 'cp936' 。
- 建议总是指定 encoding 参数,有利于跨平台。
- 如果不指定 encoding 参数,Python 会采用当前平台的默认编码格式,取决于
- 处理文件内容的基本流程:
- 调用 open() 函数来打开文件,它会返回一个文件对象。
- 调用文件对象的方法,比如 read() 读取数据、 write() 写入数据。
- 调用文件对象的 close() 方法来关闭文件。
- 进程每打开一个文件,会自动分配一个 int 型的文件描述符,相当于文件编号。
- 在 Windows 系统上,如果一个文件已经被某个进程打开了,就不允许该文件被其它进程修改,直到前一个进程释放文件描述符。
# 文件模式
open() 函数中,mode 参数可用于指定文件的读写方式,取值如下:
r
- :read ,只读模式,默认采用这种。
- 如果文件不存在,则会报错:
FileNotFoundError: No such file or directory
w
- :write ,只写模式。
- 如果文件不存在,则自动创建它,再进行写入。
- 如果文件已存在,则将长度截断为 0 ,再进行写入。这样是完全覆盖式写入。
a
- :append ,追加写模式。
- 如果文件不存在,则自动创建它,再进行写入。
- 如果文件已存在,则到文件尾部进行追加写入。
x
- :create ,先创建文件,再以只写模式打开。
- 如果文件已存在,则会报错:
FileExistsError: [Errno 17] File exists
+
- :可读可写模式。
- 必须与 r、w、a、x 模式其中之一组合使用,比如
r+
、w+
。 r+
模式支持部分覆盖式写入,比如通过 seek()+write() 修改文件中的部分内容。
open() 函数中,mode 参数也可用于指定文件流的类型,取值如下:
b
- :binary ,二进制模式,读写的值是 bytes 类型。
- 此时文件对象绑定的 IO 对象为二进制流,又称为字节流。
- 所有类型的文件都是以二进制格式存储的,因此可以按二进制格式直接读写。不过通常将二进制值转换成字节,便于阅读。
t
- :text ,文本模式,读写的值是 str 类型。
- 此时文件对象绑定的 IO 对象为文本流,又称为字符流。
- 与二进制模式相比,文本模式更方便让人阅读,但只适合打开文本格式的文件,不适合打开图片、音频等特殊编码的文件。
- 读取文件时,需要先读取文件中存储的二进制数据,再按某种编码格式转换成 str 类型。
- 写入文件时,需要先将写入的 str 类型的值,按某种编码格式转换成 bytes 类型,再写入文件。
- 打开文件时,t、b 模式只能二选一。
- 而且必须与一种读写方式组合使用,比如
rt
、rb
、rb+
。 - 只指定读写方式时,默认采用 t 模式,比如
r
相当于rt
。
- 而且必须与一种读写方式组合使用,比如
# 缓冲区
- 读写文件时,默认启用了缓冲区,但没有缓存。
- 缓冲(buffer)
- 原理:读写数据时,等数据累积一定字节数之后,再进行实际的磁盘 IO 操作。
- 优点:减少了进行磁盘 IO 的次数,减少了 CPU 等待磁盘 IO 的时间。
- 缺点:读写数据有一定延迟。如果进程突然挂掉,数据就会来不及处理而丢失。
- 例如:用 write() 方法写入数据到文件时,如果缓冲区没满,就不会立即修改磁盘中的文件。
- 缓存(cache)
- 原理:读取数据时,如果操作完数据,并不直接丢弃,而是临时保存一段时间。
- 优点:方便再次读取该数据。
- 缺点:会占用一定的缓存空间。
- 例如:可以创建一个 io.BytesIO() 对象,在内存中缓存数据。
- 缓冲(buffer)
- open() 函数中,buffering 参数可取值:
0
:取消缓冲,仅在二进制模式可用。1
:采用行缓冲,仅在文本模式可用。n
> 0 :采用 n 字节大小的缓冲区。- 默认不指定缓冲策略,如果文件为 tty ,则采用行缓冲。其它文件采用系统默认大小的缓冲区,取决于
io.DEFAULT_BUFFER_SIZE
,通常为 8 KB 。
- 调用 flush() 方法会刷新缓冲区,它会将缓冲区中等待写入的数据立即写入、等待读取的数据立即读取。
- 调用 close() 方法时会自动调用 flush() 方法。
# 换行符
读写文件时,可以声明换行符 newline ,用于自动划分每行文本。
- 比如类 Unix 系统默认是
\n
,Windows 系统中的换行符默认是\r\n
。
- 比如类 Unix 系统默认是
open() 函数中,newline 参数可取值:
- 默认为 newline=None :
- 读取文件时,会将文件中的 '\r'、'\n'、'\r\n' 都识别成换行符,并且统一转换成 '\n' ,再返回。
- 写入文件时,会将待写文本中的 '\n' 都替换成当前平台的默认换行符(取决于
os.linesep
),再写入文件。
- 如果指定了 newline='' :
- 读取文件时,会将文件中的 '\r'、'\n'、'\r\n' 都识别成换行符,然后返回该行文本。
- 写入文件时,会直接写入。
- 如果指定了 newline 等于其它值:
- 读取文件时,只会识别指定的换行符,然后返回该行文本。
- 写入文件时,会将待写文本中的 '\n' 都替换成指定的换行符,再写入文件。
- 默认为 newline=None :
# 关闭文件
- 操作完文件之后,应该调用 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
- 例:
# 常用属性
文件对象的常用属性:
>>> 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 <_io.BufferedWriter name='1.txt'> >>> f.buffer.mode 'wb'
- 以文本模式打开文件时,实际上是先用二进制模式打开文件,再转换成文本模式。
- 上例中的 f.buffer 是通过一个 BinaryIO 对象,存储了文件的二进制内容。注意它并不是读写文件时的缓冲区。
- 以文本模式打开文件时,实际上是先用二进制模式打开文件,再转换成文本模式。
# 常用方法
文件对象的常用方法:
.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()
# 文件指针
- 读写文件时,Python 会通过文件指针记录当前处理到文件中的哪个字节了。
- 使用 r、w 模式打开文件时,指针最初指向第 0 个字节。
- 每次读、写 n 个字节,Python 就会自动将指针的位置向后移动 n 个字节。
- 使用 a 模式打开文件时,每次读、写操作之前,都会自动将指针指向最后一个字节。
- 可以调用 f.seek() ,但每次读、写操作之前依然会将指针指向最后一个字节。
- 相关方法:
.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(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')