如何使用 Python 文件对象读写文件?以及文件指针,关闭文件,获取文件对象信息等问题

我被代码海扁署名-非商业-禁演绎
阅读 28:37·字数 8587·更新 
Bilibili 空间
关注 950

本节主要讲述如何使用 Python 文件对象读写文件,至于如何打开文件,以及编码,换行,缓冲等问题,请参考如何使用 Python open 函数打开文件?以及编码,解码,换行,缓冲等问题一节。

Python 文件对象

Python 文件对象负责文件的读取,写入,关闭等操作,通常作为内置函数open的返回值,根据所传递的参数的不同,open函数返回的 Python 文件对象可能是以下类型之一,TextIOWrapper(文本文件),BufferedReader(拥有缓冲区的只读二进制文件),BufferedWriter(拥有缓冲区的只写二进制文件),BufferedRandom(拥有缓冲区的可读写的二进制文件),FileIO(禁用缓冲区的二进制文件)。

使用 Python 文件对象读取文件中的字符或字节

当 Python 文件对象允许读取时,可以使用read方法读取本文文件中的字符或二进制文件中的字节,在读取的同时,文件指针将被移动。如果是读取字符,那么read方法返回 Pythonstr字符串,如果是读取字节,那么read方法返回 Pythonbytes字节序列(你可以将其视为另类的“字符串”)。

read(size=-1)

size 参数

size参数表示能够读取的最大字符数(文本文件)或最大字节数(二进制文件),如果该参数为None或小于0,那么将读取从当前指针位置到文件末尾的所有字符或字节。

这里需要指出,英文字母和汉字均被视为一个字符。

对于禁用缓冲区的二进制文件,Python 文件对象的readall方法可以读取从当前指针位置到文件末尾的所有字符或字节,该函数的执行效果与调用read方法但不给出size参数的效果类似。

readall()

打开文件

关于如何在 Pythonopen函数中设置缓冲策略,请查看为 Python open 函数指定缓冲策略一段。

在下面的示例中,通过read方法读取的前 4 个字节,包括“你”字的 3 个字节,以及“好”字的第一个字节。

read.py
# 请将命令行跳转至脚本文件 read.py 所在的目录,然后运行他
# 读取文件中的字符
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'读取 4 个字符:{file.read(4)}')
	print(f'读取剩余字符:{file.read()}')

# 读取文件中的字节 with open('read.txt', 'rb') as file: print(f'读取 4 个字节:{file.read(4)}') print(f'读取剩余字节:{file.read()}')
# 读取文件中的字节,但禁用缓冲区 with open('read.txt', 'rb', buffering=0) as file: print(f'读取 4 个字节:{file.read(4)}') print(f'读取剩余字节:{file.readall()}')
read.txt
你好,world!
Yes,很好
读取 4 个字符:你好,w
读取剩余字符:orld!
Yes,很好
读取 4 个字节:b'\xe4\xbd\xa0\xe5'
读取剩余字节:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'
读取 4 个字节:b'\xe4\xbd\xa0\xe5'
读取剩余字节:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'

当指针位于文件末尾时,Python 文件对象的 read 方法将返回空字符串或空字节序列

当文件指针位于文件末尾时,调用 Python 文件对象的read方法将返回空字符串''(文本文件)或空字节序列b''(二进制文件),而不是空值None

read_eof.py
# 请将命令行跳转至脚本文件 read_eof.py 所在的目录,然后运行他
with open('read.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'从文件末尾读取字符:{file.read()}')

with open('read.txt', 'rb') as file: file.read() print(f'从文件末尾读取字节:{file.read()}')
从文件末尾读取字符:
从文件末尾读取字节:b''

使用 Python 文件对象读取文件中的行

当 Python 文件对象允许读取时,可使用readline方法读取从当前指针位置到行末尾或文件末尾的所有字符或字节(包括换行符和回车符,如果是文本文件,那么由 Pythonopen函数的newline参数决定),并可以指定能够读取的最大字符数或最大字节数。

Python 文件对象的readline方法的返回值是字符串或bytes字节序列,在读取字符的同时,文件指针将被移动。

readline(size=-1)

size 参数

size参数为能够读取的最大字符数(文本文件)或最大字节数(二进制文件),默认为-1,表示没有最大字符数或最大字节数的限制。

打开文件

关于 Pythonopen函数的newline参数,你可以查看为 Python open 函数指定换行₁的处理方式一段。

在下面的示例中,由于我们分别使用read方法读取了 4 个字符和 4 个字节,因此,接下来的readline方法会从第 5 个字符和第 5 个字节开始读取。

readline.py
# 请将命令行跳转至脚本文件 readline.py 所在的目录,然后运行他
# 读取文本文件中的行
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'读取 4 个字符:{file.read(4)}')
	# 包括 \n
	print(f'读取行:{file.readline()}')
	print(f'读取行(最多 2 个字符):{file.readline(2)}')
	print(f'读取行:{file.readline()}')

# 读取二进制文件中的行 with open('read.txt', 'rb') as file: print(f'读取 4 个字节:{file.read(4)}') # 包括 \r\n print(f'读取行:{file.readline()}') print(f'读取行(最多 2 个字节):{file.readline(2)}') print(f'读取行:{file.readline()}')
读取 4 个字符:你好,w
读取行:orld!

读取行(最多 2 个字符):Ye
读取行:s,很好
读取 4 个字节:b'\xe4\xbd\xa0\xe5'
读取行:b'\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\n'
读取行(最多 2 个字节):b'Ye'
读取行:b's\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd'

当指针位于文件末尾时,Python 文件对象的 readline 方法将返回空字符串或空字节序列

当文件指针位于文件末尾时,调用 Python 文件对象的readline方法将返回空字符串''(文本文件)或空字节序列b''(二进制文件),而不是空值None

readline_eof.py
# 请将命令行跳转至脚本文件 readline_eof.py 所在的目录,然后运行他
with open('read.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'从文件末尾读取一行:{file.readline()}')

with open('read.txt', 'rb') as file: file.read() print(f'从文件末尾读取一行:{file.readline()}')
从文件末尾读取一行:
从文件末尾读取一行:b''

当 Python 文件对象允许读取时,可使用readlines方法读取从当前指针位置到文件末尾的所有行(包括换行符和回车符,如果是文本文件,由 Pythonopen函数的newline参数决定),并可以指定能够读取的最大字符数或最大字节数,在读取的同时,文件指针将被移动。

Python 文件对象的readlines方法的返回值是一个列表对象,列表中的元素对应从文件读取到的行(一个字符串或字节序列)。

readlines(hint=-1)

hint 参数

hint参数为能够读取的最大字符数(文本文件)或最大字节数(二进制文件),默认为-1,表示没有最大字符数或最大字节数的限制。

需要说明的是,即便已经超出了最大字符数或最大字节数,Python 文件对象的readlines方法依然会读取至行的末尾或文件末尾。

在下面的示例中,我们分别使用readlines方法读取了 12 个字符和 12 个字节,文件指针将到达第二行和留在第一行,对于这两种情况,Python 文件对象均会读取至行末尾。

readlines.py
# 请将命令行跳转至脚本文件 readlines.py 所在的目录,然后运行他
# 读取文本文件中的多个行
with open('readlines.txt', 'r', encoding='utf8') as file:
	print(f'读取多个行(最多 12 个字符):{file.readlines(12)}')
	print(f'读取剩余的行:{file.readlines()}')

# 读取二进制文件中的多个行 with open('readlines.txt', 'rb') as file: print(f'读取多个行(最多 12 个字节):{file.readlines(12)}') print(f'读取剩余的行:{file.readlines()}')
readlines.txt
你好,world!
Yes,很好
What a day!
读取多个行(最多 12 个字符):['你好,world!\n', 'Yes,很好\n']
读取剩余的行:['What a day!']
读取多个行(最多 12 个字节):[b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8cworld\xef\xbc\x81\r\n']
读取剩余的行:[b'Yes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd\r\n', b'What a day!']

当指针位于文件末尾时,Python 文件对象的 readlines 方法将返回空的列表

当文件指针位于文件末尾时,调用 Python 文件对象的readlines方法将返回空的列表[],而不是空值None

readlines_eof.py
# 请将命令行跳转至脚本文件 readlines_eof.py 所在的目录,然后运行他
with open('readlines.txt', 'r', encoding='utf8') as file:
	file.read()
	print(f'从文件末尾读取多个行:{file.readlines()}')

with open('readlines.txt', 'rb') as file: file.read() print(f'从文件末尾读取多个行:{file.readlines()}')
从文件末尾读取多个行:[]
从文件末尾读取多个行:[]

通过遍历 Python 文件对象读取文件中的行

除了使用方法readlinereadlines来读取文件中的行,你还可以通过遍历 Python 文件对象的方式来读取行,因为文件对象也是迭代器对象。

for.py
# 请将命令行跳转至脚本文件 for.py 所在的目录,然后运行他
# 使用 for 语句遍历文件
with open('readlines.txt', 'r', encoding='utf8') as file:
	for l in file:
		print(l)
你好,world!

Yes,很好

What a day!

使用 Python 文件对象将二进制文件中的字节读取至类字节序列对象

对于二进制文件,你可以使用 Python 文件的readinto方法,将文件中的字节读取至类字节序列(bytes-like)对象。readinto方法会尝试读取足够多的字节,以填充类字节序列对象,并将实际读取的字节数作为返回值(int类型)。

readinto(b)

b 参数

b参数是一个可以写入的类字节序列对象。

在下面的示例中,我们尝试将二进制文件中剩余的 23 个字节读取至bytearray,因此bytearray的最后 7 个字节为\x00

readinto.py
# 请将命令行跳转至脚本文件 readinto.py 所在的目录,然后运行他
# 读取字节至 bytearray
with open('read.txt', 'rb') as file:
	ba8 = bytearray(8)
	# 尝试读取 8 个字节以填充 ba
	print(f'读取了 {file.readinto(ba8)} 个字节')
	print(ba8)

ba30 = bytearray(30) # 读取剩余的 23 个字节 print(f'读取了 {file.readinto(ba30)} 个字节') print(ba30)
读取了 8 个字节
bytearray(b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc')
读取了 23 个字节
bytearray(b'\x8cworld\xef\xbc\x81\r\nYes\xef\xbc\x8c\xe5\xbe\x88\xe5\xa5\xbd\x00\x00\x00\x00\x00\x00\x00')

使用 Python 文件对象将字符或字节写入文件

当 Python 文件对象允许写入时,可使用write方法将字符或字节写入本文文件或二进制文件,并返回写入的字符数或字节数。

write(s|b)

s,b 参数

s参数为需要写入的字符串,b参数为包含需要写入的字节的类字节序列对象。

utf8编码中的大部分汉字占用 3 个字节,字符串'第一行\n第二行'在解码后是一个包含 19 个字节的bytes对象。

write.py
# 请将命令行跳转至脚本文件 write.py 所在的目录,然后运行他
# 将字符写入文件
with open('write.txt', 'w', encoding='utf8') as file:
	c = file.write('第一行\n第二行')
	print(f'写入 {c} 个字符')

# 将字节写入文件 with open('writeb.txt', 'wb') as file: bs = '第一行\n第二行'.encode() c = file.write(bs) print(f'写入 {c} 个字节')
写入 7 个字符
写入 19 个字节

使用 Python 文件对象将行写入文件

当 Python 文件对象允许写入时,可以使用writelines方法将多行数据(字符或字节)写入本文文件或二进制文件。

writelines(lines)

lines 参数

lines参数是一个迭代器对象,迭代器中的元素是需要写入的字符串,或包含需要写入的字节的类字节序列(bytes-like)对象。

Python 文件对象的 writelines 方法不会自动为每一行添加换行符或回车符

Python 文件对象的writelines方法并不会自动为每一行添加换行符或回车符(或者换行符或回车符对应的字节),因此需要开发人员自行处理。

在下面的示例中,我们自行为第一行添加换行符和其对应的字节。

writelines.py
# 请将命令行跳转至脚本文件 writelines.py 所在的目录,然后运行他
# 将多个行写入文件
with open('writelines.txt', 'w', encoding='utf8') as file:
	file.writelines(('第一行\n', '第二行'))

# 将多个行写入文件 with open('writelinesb.txt', 'wb') as file: file.writelines(('第一行\n'.encode(), '第二行'.encode()))

使用 Python 文件对象将缓冲区中的内容写入文件

对于支持写入操作并使用了缓冲区的 Python 文件对象,其方法flush可用于立即将缓冲区中的内容写入文件,当然,很多情况下,调用该方法是没有必要的。

对于不支持写入操作或禁用了缓冲区的 Python 文件对象,调用flush方法不会产生任何效果。

flush()

在下面的示例中,由于缓冲区的大小为4b'abc'会保留在缓冲区中,因为他们仅占用 3 个字节。

flush.py
# 请将命令行跳转至脚本文件 flush.py 所在的目录,然后运行他
with open('flush.txt', 'wb', buffering=4) as file:
	file.write(b'abc')
	# 将缓冲区中的 abc 写入文件
	file.flush()

使用 Python 文件对象获取和设置文件指针的位置

在绝大多数情况下,文件指针的位置是 Python 文件对象从文件读取内容或将内容写入文件的开始位置,并且会在读取或写入内容后发生变化,以方便下一次的读取和写入。在打开文件之后,文件指针的位置可能在文件开头或末尾。

无论是文本文件还是二进制文件,文件指针的位置总是以字节为单位,因此,对于文本文件对应的 Python 文件对象,需要得知字符对应的字节数才能正确的移动文件指针,否则可能导致错误的发生。

Python 文件对象提供了名称为tell的方法,用于获得当前文件指针的位置,该方法返回一个整数,如果返回值为0,则表示指针位于文件的开头。

tell()

Python 文件对象提供了名称为seek的方法,用于改变文件指针的位置。

seek(offset, whence)

offset 参数

offset参数是一个表示文件指针偏移值的整数,当offset大于0时,文件指针将向文件末尾移动指定的字节数,当offset小于0时,文件指针将向文件开头移动指定的字节数。

whence 参数

whence参数是一个整数,如果为0(默认值,os.SEEK_SET),则从文件的开头位置计算offset表示的指针偏移值,如果为1os.SEEK_CUR),则从当前文件指针位置计算offset表示的指针偏移值,如果为2os.SEEK_END),则从文件的末尾位置计算offset表示的指针偏移值。

在下面的示例中,我们打开了文本文件read.txt,因此要移动文件指针的位置,需要知道字符所占用的字节数,这里文件开头处的“你好”两字占用了 6 个字节。

pointer.py
# 请将命令行跳转至脚本文件 pointer.py 所在的目录,然后运行他
file = open('read.txt', 'r', encoding='utf8')

# 开始时指针位于文件开头 print(f'指针位置:{file.tell()}') # 将指针移动到“你好”之后,“你好”占用了 6 个字节 file.seek(6, 0) # 读取下一个字符“,” print(file.read(1)) # 由于“,”占用 3 个字节,因此指针位置现在为 9 print(f'指针位置:{file.tell()}')
指针位置:0

指针位置:9

Python 文件对象不能将文件指针的位置设置为负数

无论如何,Python 文件对象无法将文件指针的位置设置为负数,否则将导致异常。不过,使文件指针的位置超出文件末尾并没有问题。

在下面的示例中,我们将文件指针移动到文件末尾后 100 的位置,但之后的read方法并没有抛出异常。

pointer_err.py
# 请将命令行跳转至脚本文件 pointer_err.py 所在的目录,然后运行他
file = open('read.txt', 'r+b')

# 文件指针位置将超出文件末尾 file.seek(100, 2) print(f'指针位置:{file.tell()}') # 读取文件不会产生异常 print(file.read())
# ERROR 文件指针的位置不能是负数 file.seek(-100, 0)
指针位置:131
b''
OSError: [Errno ] Invalid argument

判断 Python 文件对象是否支持随机访问

当 Python 文件对象不支持文件的随机访问时,你将不能获取或设置文件指针的位置,即不能调用文件对象的tellseek方法,否则会引发异常OSError

通过 Python 文件对象的seekable方法,可以判断文件对象是否支持随机访问,如果该方法返回True,那么表示支持随机访问。

seekable()

判断 Python 文件对象是否可以读取或写入

Python 文件对象的readablewritable方法返回一个布尔值,用于表示文件对象是否支持读取和写入操作(主要由 Pythonopen函数的mode参数决定),返回True表示支持。

readable()
writable()

打开文件

关于 Pythonopen函数的mode参数,你可以查看为 Python open 函数指定文件打开模式一段。

info.py
# 请将命令行跳转至脚本文件 info.py 所在的目录,然后运行他
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'读取:{file.readable()},写入:{file.writable()}')

with open('read.txt', 'r+b') as file: print(f'读取:{file.readable()},写入:{file.writable()}')
读取:True,写入:False
读取:True,写入:True

使用 Python 文件对象关闭文件

Python 文件对象拥有一个名称为close的方法,用于关闭文件对象对应的文件,一旦该方法被调用,你将无法通过文件对象对文件实施操作,比如,读取和写入。

close()

确保在代码中调用 Python 文件对象的 close 方法

虽然 Python 文件对象会在销毁时尝试关闭文件,但在代码中明确的调用 Python 文件对象的close方法,依然是保险的做法,否则通过文件对象写入的内容存在无法保存至文件的可能。

如果使用 Pythonwith语句和open函数打开文件,那么不需要调用文件对象的close方法,因为,该方法会被文件对象的__exit__方法直接或间接调用。

with 语句

关于 Python 的with语句,你可以查看Python with 语句,with 语句上下文管理器介绍一节。

在下面的示例中,由于文件对象已经调用了close方法,因此,再次通过文件对象写入数据将导致异常。

close.py
# 请将命令行跳转至脚本文件 close.py 所在的目录,然后运行他
file = open('close.txt', 'w', encoding='utf8')
file.write('写入一些数据!')
# 关闭文件
file.close()

# ERROR 文件已经关闭 file.write('再写入一些数据?')
ValueError: I/O operation on closed file.

判断 Python 文件对象对应的文件是否已经关闭

通过 Python 文件对象的closed属性,可以检查文件对象对应的文件是否已经关闭,如果closed属性为True,那么表明文件已经关闭。

closed

在下面的示例中,我们通过with语句打开文件,在with语句结束时,文件对象的__exit__方法将被调用,而__exit__方法将调用close方法关闭文件。

closed.py
# 请将命令行跳转至脚本文件 closed.py 所在的目录,然后运行他
# with 语句结束之后,文件将被关闭
with open('close.txt', 'w', encoding='utf8') as file:
	file.write('写入一些数据!')

# 检查文件是否被关闭 if file.closed: print('文件 close.txt 已经关闭')
文件 close.txt 已经关闭

使用 Python 文件对象截断文件

对于支持写入的 Python 文件对象,可以使用truncate方法将文件截断,并返回截断后的大小,该方法不会改变文件指针的位置。

truncate(size=None)

size 参数

size参数是一个整数(大于等于0),表示在第几个字节处截断文件。如果该参数被忽略或者为None,则在当前文件指针的位置截断文件(注意,实际效果可能并非如此)。

事实上,size参数所指示的位置可以超出文件末尾,这会导致文件的末尾被填充若干字节\x00

在下面的示例中,“真是美好”占用了 12 个字节,调用方法truncate之后文件指针没有发生变化。

truncate.py
# 请将命令行跳转至脚本文件 truncate.py 所在的目录,然后运行他
with open('truncate.txt', 'r+', encoding="utf8") as file:
	# 显示文件内容
	print(file.read())
	# 保留“真是美好”
	print(f'从第 12 个字节处截断:{file.truncate(12)}')
	# 文件指针位置不会被 truncate 方法改变
	print(f'当前指针位置:{file.tell()}')
truncate.txt
真是美好的一天
真是美好的一天
从第 12 个字节处截断:12
当前指针位置:21

获取 Python 文件对象的其他信息

在使用 Python 内置函数open创建文件对象时,我们会使用一些参数来设置文件对象的某些功能,而文件对象则提供了以下变量,属性,方法来说明他们。

Python 文件对象的name属性是文件路径或文件描述器(file descriptor)对应的整数,这与传递给open函数的file参数有关。

Python 文件对象的fileno方法返回文件描述器对应的整数,是的,即便你向open函数传递文件路径,但最终使用的是文件描述器。

Python 文件对象的mode属性表示打开模式,这与传递给open函数的mode参数有关。

name
fileno()
mode

对于文本文件,Python 文件对象的encoding变量表示编码格式,errors变量表示编码或解码错误的处理方式,他们均与传递给open函数的同名参数有关。

对于文本文件,Python 文件对象的line_buffering属性是一个布尔值,表示是否使用了行缓冲,如果为True,那么表示使用了行缓冲。

encoding
errors
line_buffering

对于禁用缓冲区的二进制文件,Python 文件对象的closefd属性表示当文件关闭时是否同时关闭文件描述器,这与传递给open函数的closefd参数有关。

closefd

info.py
# …
with open('read.txt', 'r', encoding='utf8') as file:
	print(f'name {file.name}')
	print(f'fileno {file.fileno()}')
	print(f'mode {file.mode}')
	print(f'encoding {file.encoding}')
	print(f'errors {file.errors}')
	print(f'line_buffering {file.line_buffering}')

import os fd = os.open('read.txt', os.O_RDONLY | os.O_BINARY) with open(fd, 'rb', buffering=0, closefd=False) as file: print(f'name {file.name}') print(f'closefd {file.closefd}')
name read.txt
fileno 3
mode r
encoding utf8
errors strict
line_buffering False
name 3
fileno 3
closefd False

源码

src/zh/file_system/file_handling/file_objects·codebeatme/python·GitHub