如何使用 Python 文件对象读写文件?以及文件指针,关闭文件,获取文件对象信息等问题
本节主要讲述如何使用 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 所在的目录,然后运行他
# 读取文件中的字符
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()}')
你好,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 所在的目录,然后运行他
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 所在的目录,然后运行他
# 读取文本文件中的行
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 所在的目录,然后运行他
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 所在的目录,然后运行他
# 读取文本文件中的多个行
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()}')
你好,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 所在的目录,然后运行他
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 文件对象读取文件中的行
除了使用方法readline
和readlines
来读取文件中的行,你还可以通过遍历 Python 文件对象的方式来读取行,因为文件对象也是迭代器对象。
# 请将命令行跳转至脚本文件 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 所在的目录,然后运行他
# 读取字节至 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 所在的目录,然后运行他
# 将字符写入文件
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 所在的目录,然后运行他
# 将多个行写入文件
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()
在下面的示例中,由于缓冲区的大小为4
,b'abc'
会保留在缓冲区中,因为他们仅占用 3 个字节。
# 请将命令行跳转至脚本文件 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
表示的指针偏移值,如果为1
(os.SEEK_CUR
),则从当前文件指针位置计算offset
表示的指针偏移值,如果为2
(os.SEEK_END
),则从文件的末尾位置计算offset
表示的指针偏移值。
在下面的示例中,我们打开了文本文件read.txt
,因此要移动文件指针的位置,需要知道字符所占用的字节数,这里文件开头处的“你好”两字占用了 6 个字节。
# 请将命令行跳转至脚本文件 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 所在的目录,然后运行他
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 文件对象不支持文件的随机访问时,你将不能获取或设置文件指针的位置,即不能调用文件对象的tell
和seek
方法,否则会引发异常OSError
。
通过 Python 文件对象的seekable
方法,可以判断文件对象是否支持随机访问,如果该方法返回True
,那么表示支持随机访问。
seekable()
判断 Python 文件对象是否可以读取或写入
Python 文件对象的readable
和writable
方法返回一个布尔值,用于表示文件对象是否支持读取和写入操作(主要由 Pythonopen
函数的mode
参数决定),返回True
表示支持。
readable()
writable()
打开文件
关于 Pythonopen
函数的mode
参数,你可以查看为 Python open 函数指定文件打开模式一段。
# 请将命令行跳转至脚本文件 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 所在的目录,然后运行他
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 所在的目录,然后运行他
# 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 所在的目录,然后运行他
with open('truncate.txt', 'r+', encoding="utf8") as file:
# 显示文件内容
print(file.read())
# 保留“真是美好”
print(f'从第 12 个字节处截断:{file.truncate(12)}')
# 文件指针位置不会被 truncate 方法改变
print(f'当前指针位置:{file.tell()}')
真是美好的一天
真是美好的一天
从第 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
# …
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