前言
哈喽大家好,我是知道,在计算机的世界里,文件操作是很常见的,无论是在操作系统里直接操作文件还是使用编程语言操作文件。大多数情况下文件都需要进行文件的持久化,也叫落地,由于内存有掉电易失特性,所以一般是放在磁盘上,既然是操作文件就需要磁盘的IO操作,相比于放内存里,访问速度就会慢很多,所以遇到IO操作时需要考虑操作是否频繁,是否会影响性能等。接下来我们对python里的文件操作进行一个知识梳理。
1. 操作文件常用方法
python中操作文件有以下方法:
| 方法 | 说明 |
|---|---|
| open | 打开文件 |
| read | 读取文件内容(文本读字符和二进制读字节) |
| write | 写入内容 |
| close | 关闭文件 |
| readline | 按行读取内容 |
| readlines | 多行读取内容 |
| seek | 操作指针 |
| tell | 显示指针位置 |
其中最常用的方法有打开,读取,写入,关闭。文件只有在打开之后才可以进行其他操作,比如读、写(读写过程是可以循环多次的);一系列文件操作之后别忘记关闭,这里留一个问题,为什么操作之后都需要进行关闭呢?后文中会给出解释。
2. 打开
在python中,通过open方法打开文件,返回文件流对象,可迭代,打开失败会抛出OSError异常。 open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)
2.1 基本使用
创建文件夹test,并以此为项目根目录使用pycharm打开,在test目录下创建文件hello.txt并写入内容"This is hello.txt"
# file对象,<class '_io.TextIOWrapper'>
f = open("hello.txt")
print(f)
# windows下输出
>>> <_io.TextIOWrapper name='hello.txt' mode='r' encoding='cp936'>
# linux下输出
>>> <_io.TextIOWrapper name='hello.txt' mode='r' encoding='UTF-8'>
content = f.read()
print(type(content), content)
>>> <class 'str'> This is hello.txt
f.close()
2.2 open方法常用参数
2.2.1 file
指打开或者要创建的文件名。如果不指定路径,则默认当前路径。
2.2.2 mode
以什么模式打开文件,用来限定文件对象的操作及文本访问,比如只读,只写等。有以下模式:
| 描述字符 | 说明 |
|---|---|
| r | 只读打开,默认模式 |
| w | 只写打开 |
| x | 创建并写入一个新文件 |
| a | 写入打开,如果文件存在,则尾部追加写 |
| b | 二进制模式 |
| t | 文本模式,默认模式 |
| + | 给只读只写方式提供读写能力 |
模式r, w, a, x
# r模式,默认模式
# f = open("hello.txt")
f = open("hello.txt", "r")
content = f.read()
print(content)
>>> This is hello.txt
f.write("QQQ")
f.close()
>>> 会出现什么结果?
# w模式,打开已有文件并写入
f = open("hello.txt", "w")
f.read()
f.close()
>>> 会出现什么结果?
f = open("hello.txt", "w")
f.write("write success!")
f.close()
>>> 查看一下hello.txt文件会有什么结果?
# 创建新文件hello1.txt
f = open("hello1.txt", "w")
f.write("This is hello1.txt")
f.close()
>>> 查看一下当前目录,是否多出一个新文件?文件内容是什么?
# x模式,打开已有文件
f = open("hello.txt", "x")
>>> 会出现什么现象?
f = open("hello2.txt", "x")
f.write("This is hello2.txt")
f.read()
>>> 会出现什么现象?
f = open("hello2.txt", "x")
f.write("This is hello2.txt")
f.close()
>>> 查看当前目录是否有新文件产生,如果有,看下内容
# a模式,打开已有文件
f = open("hello.txt", "a")
print(f.read())
>>> 会有什么现象?
f = open("hello.txt", "a")
f.write("append success")
f.close()
>>> 查看已有文件内容,发现了什么?
f = open("hello3.txt", "a")
f.write("This is hello3.txt with mode 'a' opening")
f.close()
>>> 查看当前目录有什么变化,查看文件内容
小结: open方法默认只读模式r打开已存在文件;r模式下:只读模式打开文件,使用write方法会抛异常,io.UnsupportedOperation;如果文件路径错误,会抛异常FileNotFoundError。w模式下:只写模式打开文件,如果使用read方法会抛异常,io.UnsupportedOperation;打开已有文件,会清空文件内容;如果文件不存在,则会新建文件。x模式下:文件存在,会抛FileExistsError异常;文件不存在则创建新文件,只写模式,不可读取,读取则io.UnsupportedOperation异常。a模式下:只写打开,文件已存在,则追加写入;文件不存在,则新建文件并追加写入。
文本模式t与二进制模式b
文本模式t,字符流,将文件按照指定字符编码,按照字符进行操作。open默认mode是rt, 二进制模式b,字节流,将文件按照字节打开,按照bytes类型进行操作,与字符编码无关。
# 二进制只读
f = open("hello.txt", "rb")
content = f.read()
print(type(content))
print(content)
f.close()
# 二进制只写
f = open("hello.txt", "wb")
f.write("This is mode 'wb' test")
>>> 会出现什么情况?
f = open("hello.txt", "wb")
# encode()默认utf-8编码
res = f.write("This is mode 'wb' test".encode())
print(res) # 字节数
f.close()
+模式
f = open("hello.txt", "rw")
>>> 会出现什么?
f = open("hello.txt", "r+")
# 与文件原先的内容作比较
print("1-------", f.read())
>>> 显示什么内容?
f.write("mode 'r+' test")
print("2-------", f.read())
f.close()
>>> 会出现什么现象?
f = open("hello.txt", "r+", encoding="utf-8")
f.write("mode 'r+' test".)
print(f.read())
>>> 显示什么?想一想为什么?
f.close()
f = open("hello.txt", "w+")
print(f.read())
>>> 显示什么?为什么?
f.close()
f = open("hello.txt", "a+")
f.write("a+ test")
print(f.read())
>>> 显示什么?为什么?
f.close()
f = open("hello4.txt", "x+")
f.write("x+ test")
print(f.read())
>>> 显示什么?为什么?
f.close()
小结: +模式为r, w, a, x提供确实的读写功能,但获取文件对象时,仍按照原有的模式特征;+模式不能单独使用。既然+模式是为其他基础功能的一次增强,但是实际使用的时候,所增强的功能虽然没有报错,但好像也没有生效,这是为什么呢?此时不得不提的就是文件指针。 文件指针,指当前字节位置。有如下特点及操作:r模式下,指针起始位置为0;a模式下,指针起始位置为末尾EOF(End Of File);tell()显示当前指针位置;seek(offset[, whence]),移动文件指针位置。其中offset为偏移字节量,whence指从哪里开始。 文本模式下: whence 0 为默认值,表示从头开始,offset只能正整数;whence 1 ,表示从当前位置开始,offset只能为0;whence 2 ,表示从EOF开始,offset只能为0。
f = open("tell_test.txt", "w+", encoding="utf-8")
f.write("这是tell的测试")
print("1----------", f.tell())
print("2----------", f.read())
print("3----------", f.tell())
>>> 会有输出吗?输出是什么?
f.seek(0)
print("4----------", f.tell())
print("5----------", f.read())
>>> 会有输出吗?输出是什么?
print("6----------", f.tell())
f.seek(1)
print("7----------", f.read(1))
>>> 与想象中的输出一致吗?
f.close()
小结: 文本模式支持从头开始向后偏移方式;whence为1表示,从当前位置开始偏移,但只支持偏移为0;whence为2表示从EOF开始偏移,只支持偏移0;seek是按照字节偏移的;read在文本模式下按照字符读取。 二进制模式下: whence 0 为默认值,表示从头开始,offset只能正整数;whence 1 ,表示从当前位置开始,offset可正可负;whence 2 ,表示从EOF开始,offset可正可负。
f = open("tell_test.txt", "rb+")
print("1----------", f.tell())
print("2----------", f.read())
print("3----------", f.tell())
f.write(b"qwer")
f.seek(0)
f.seek(1, 1) # 从当前指针开始,向后偏移1
print("4----------", f.read())
f.seek(-1, 1) # 从当前指针开始,向前偏移1
print("5----------", f.read())
f.seek(1, 2)
f.seek(0)
f.seek(-1, 2)
print("6----------", f.read())
f.seek(0)
f.seek(100, 1)
f.seek(0)
f.seek(-100, 2)
f.close()
>>> 输出结果与预期是否一致?
小结: 二进制模式支持任意起点位置的偏移,头尾中间均可;正向seek可以超界,反向seek不可超界,会抛异常。 注: 在文本模式下最好不要使用seek中间某个位置,因为seek是按字节来的,有可能将中文拆出一部分从而报错UnicodeDecodeError,一个中文三个(utf-8)或者两个字节(GBK);文本及bytes模式,seek时左边界不能超,右边界超的时候会在中间补ascii码0;文件指针大多数情况下用在二进制模式。
2.2.3 buffering
在操作系统中,与磁盘打交道是很耗时的操作,因为写入磁盘相对来说太慢了,所以一般IO设备都会有缓冲区,在写入的时候,先写入缓冲区,在达到一定条件(比如缓冲区满了,或者达到设定的阈值,又或者强制flush写入等)的时候再写入磁盘,一批一批地写入相对于不断地与磁盘进行交互写入,效率高很多。缓冲区是由操作系统分配地,一般都在内存中,所以操作会很快。 注:以下测试用例最好在Linux系统或者交互式python界面测试,IDE可能会影响结果
import io
# 查看系统默认缓冲区大小,单位是字节
print(io.DEFAULT_BUFFER_SIZE)
f = open("buffer_test.txt", buffering=0)
=> 输出什么?说明什么?
# 二进制模式,一般情况下用的很少,因为要和磁盘交互,性能会有很大问题
f = open("buffer_test.txt", "rb+", buffering=0)
f.write(b"1")
=> 查看文件内容,内容是否立即落地?查看之后再关闭文件
f.close()
f = open("buffer_test.txt", "w+", buffering=1)
f.write("123")
=> 查看下文件内容
f.close()
=> 再次查看下文件内容
f = open("buffer_test.txt", "w+", buffering=1)
f.write("abcdefg" * 200)
=> 再次查看下内容,如果还没有内容,继续写入,直到有内容写入为止
f.close()
f = open("buffer_test.txt", "w+", buffering=1)
f.write("123")
f.write("\n")
=> 查看下文件内容,发现什么?
f.close()
f = open("buffer_test.txt", "w+", buffering=1)
f.write("123")
f.write("q\nw")
=> 查看下文件内容,发现什么?
f.close()
f = open("buffer_test.txt", "w+", buffering=1)
f.write("123")
f.flush()
=> 查看下文件内容,发现什么?
f.close()
* 将buffering=1的模式换成wb+试试看
----------------------------------------------------
# 设置缓冲区8个字节
f = open("buffer_test.txt", "wb+", buffering=8)
f.write(b"12345678")
=> 查看文件里是否有内容
f.write(b"9")
=> 再次查看文件内容
* 将buffering>1的模式换成文本模式试试看
小结: python在写文件时,当缓冲区满达到临界值时,再次执行写入操作,会将内容落地到磁盘,同时提供了强制写入磁盘方法flush,在对文件进行关闭时也会调用flush并关闭文件对象。
| buffering值 | 说明 |
|---|---|
| -1 | 默认值,适用于t和b模式,默认系统大小io.DEFAULT_BUFFER_SIZE。 |
| 0 | 支持b模式,关闭缓冲区,写一次与磁盘交互一次; 不支持t模式。 |
| 1 | t模式下,行缓冲,遇到换行符之后才将内容落地到磁盘; b模式下不受换行符影响。 |
| >1 | b模式表示自定义缓冲区大小,直到超出设定的值之后才落地到磁盘,可以超过默认大小; t模式下设置大小无效,只有超出默认大小或者flush后才会将内容写入。 |
注: 文本模式下,一般使用默认缓冲区大小;二进制模式,如果需要调缓冲区大小,一般是512的倍数;一般情况下,默认缓冲区大小即可;在编程中,明确需要写磁盘时,都会手动调用一次flush。
2.2.4 encoding
在不指定编码的情况下,open函数会采用当前系统的默认编码。None表示使用默认编码,windows默认GBK编码,linux默认UTF-8。需要注意的是写文件和读文件最好采用一样的编码,避免出现乱码,一般推荐utf-8。
2.2.5 其他不常用参数
errors:获编码错误,None或者strict表示有编码错误就抛出ValueError异常,ignore表示不抛异常,一般默认即可; newline:文本模式中,转换换行符;
f = open("newline_test.txt", "w")
f.write("a\nb\rc\r\nd")
f.close()
# 只支持这些,默认None
newlines = (None, "", "\n", "\r", "\r\n")
for nl in newlines:
f = open("newline_test.txt", newline=nl)
# 将每行内容以列表形式返回
print(f.readlines())
f.close()
=> 查看下区别
小结: 默认None情况下将所有常见换行符替换为\n;newline=""时识别换行符,什么都不做; \n、\r、 \r\n会以符号本身为界限进行切割并保留符号进行输出。
closefd:关闭文件描述符,表示是否在文件关闭后关闭文件描述符,True表示关闭,False表示不关闭。文件对象.fileno()可以查看。
3. 读操作
read(n=-1),读取文件内容,参数n表示读取多少个字节或字符,负数或None表示读取到EOF; readline(limit=-1),一行行读取文件内容,limit表示一次能读取行内几个字节或字符; readlines(hint=-1),返回读取所有行的列表,hint指定行数。
一般在开发过程中,对文件的每行做处理时,很少用到这两个函数,一般直接遍历文件对象。
f = open("test.txt", "w+")
for i in f:
# 处理i逻辑
print(i)
f.close()
4. 写操作
write(s),将字符串s写入文件并返回字符的个数; writelines(lines),将字符串列表写入文件,列表内元素只能为字符串;如果元素需要换行,需要加换行符。
s = ["a\n", "b", "c"]
f = open("write_test.txt", "w+")
f.writelines(s)
f.close()
s = ["a", "b", "c"]
f = open("write_test.txt", "w+")
f.writelines(s)
f.close()
s = ["a", "b", "c", ""]
f = open("write_test.txt", "w+")
# 不如这样操作
f.write("\n".join(s))
f.close()
5. 关闭
close(),调用flush并关闭文件对象,可多次关闭,如果已经关闭,再次关闭则没有什么效果及现象。
6. 其他操作
| 函数名 | 说明 |
|---|---|
| closed | 文件对象是否已关闭,返回True或False |
| readable() | 文件对象是否可读,返回True或False |
| writable() | 文件对象是否可写,返回True或False |
| seekable() | 文件对象是否可移动指针,返回True或False |
7. 总结
通过本篇文章,我们对基本的文件IO操作知识进行了梳理,包括文件的打开,读写等方法;文件打开的七种模式及各模式有什么特点;为什么+模式明明是补充功能却好像也没生效似的;为什么一个文件打开之后要及时关闭;为什么要设置缓冲区,缓冲区有什么作用等。希望上述例子大家能手动都敲一遍,然后参照文章总结为自己的知识,这样才能更好地吸收。同时,也欢迎大家能够随时指正文章中总结不恰当的地方,共同进步。
我是知道, 感谢各位人才的:点赞、收藏和评论,我们下期更精彩!




