大家回顾一下我们前面所学,我们程序中需要存储数据都是存储在内存中,然而当进程(运行中的程序)结束的时候, 我们的内存就会被操作系统所回收,我们的数据也就没了。
如果我们能把数据存储在磁盘中,别说是进程结束,哪怕是关机,我们的数据都不会丢失。
操作系统不允许我们自己写的普通的程序直接操作磁盘,在磁盘上读写文件的功能都是由操作系统提供的, 所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符), 然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
这种在程序中通过文件对象对磁盘进行读写的操作我们叫做磁盘 IO,当然磁盘 IO 属于文件 IO。在本节课的讲解中我们统一称磁盘 IO 为文件 IO(大家都习惯于这样称呼),我们知道 IO 是指输入和输出,也就是读写,文件 IO 我们也称之为文件读写,大家要知道这种叫法。
读文件是最常见的 IO 操作,Python 内置了读文件的函数,比如要以读文件的模式打开一个文件对象,使用 Python 内置的 open 函数,传入文件名和标识符,其中标识符 "r" 表示读文本文件,"rb" 表示读二进制文件(图片,音乐,视频等)。但是如果读取的文件不存在,open 函数就会抛出一个 IOError 的错误,并且给出错误码和详细的信息告诉你文件不存在。最后要注意,文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的,调用 close 方法关闭文件。
f = open("d:/test.txt", "r") # 要确保 d 盘下存在 test.txt 文件,否则会报异常 data = f.read() print(data) f.close() # 用完后关闭文件对象
在程序中我们成功打开一个文件进行操作,并且最后我们没有调用 close 函数关闭文件对象,进程结束,操作系统会自动回收该进程所申请的所有资源(文件对象也是资源)。 但是最好不要这样去做,我们要养成一个好的习惯,因为我们的程序很有可能循环调用多次这个 open 函数,或者我们是一个提供服务功能的程序,是需要长期一直执行的。
f = open("d:/test.txt", "r") # 要确保 d 盘下存在 test.txt 文件 data = f.read() print(data) # 进程结束,操作系统会回收改进程申请的所有资源(文件对象)
我们成功打开一个文件进行操作时,由于后面的语句错误可能导致程序异常,这样的话,程序就不会执行到我们调用文件对象的 close 函数。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用 try ... except 来实现。
try: f = open("d:/test.txt", "r") data = f.read() print(data) print(data.fuck) # 报异常,退出 try 语句,进入 except 语句 f.close() # 解释器不会执行这条语句 except: f.close() raise("文件操作出现错误")
每次操作文件都要写这种异常处理语句感觉很繁琐,这完全不符合 Python 高大上的形象, 我们可以使用 Python 引入的 with 语句来自动帮我们自动调用 close 函数。
with open("d:/test.txt", "r") as f: data = f.read() print(data) print(data.fuck) # 虽然报异常,但在 with 块内,解释器会自动调用 close 函数,关闭文件对象
当然在 with 语句内不报异常,解释器在离开 with 语句块的时候,也会调用 close 函数关闭文件对象。
with open("d:/test.txt", "r") as f: data = f.read() print(data) print(f.read()) # 报异常,因为解释器在离开 with 语句块的时候,会调用 close 函数关闭文件对象。
我们的 open 函数返回的是向操作系统申请的文件资源, 该文件资源我们也叫作文件对象或者文件描述符,同学们可能会想程序执行过程中出现异常,进程就会退出, 进程退出操作系统就会回收进程申请的所有资源,我们通过 open 函数申请的文件对象就会被操作系统所回收, 自然就不用我们调用文件对象的 close 函数了,其实如果我们程序只有一个进程倒是无所谓,但如果我们的程序有多个进程, 且这个出现异常的语句是在子进程中,子进程出现异常,我们的主进程并不会退出, 而且我们主进程经常会调用到这个向操作系统申请文件对象的崩溃的子进程,那就会耗干操作系统维护的文件资源。 在后面章节中我们会学习到多进程编程。
调用 read 函数会一次性读取文件的全部内容,如果我们读取的文件很大,就有可能出现内存不够用而失败, 所以,要保险起见,可以反复调用 read(size) 方法,每次最多读取 size 个字节的内容。 注意:不要用记事本写入文件内容,最好使用 notepad++,保存为 utf8 格式。
with open("d:/test.txt", "r") as f: while True: data = f.read(5) if data == "": # 没有数据了退出循环 break print(data)
调用 readline 函数可以每次读取一行内容。
with open("d:/test.txt", "r") as f: while True: dataline = f.readline() if dataline == "": # 没有数据了退出循环 break print(dataline)
调用 readlines 函数一次读取所有内容并按行返回 list。
with open("d:/test.txt", "r") as f: datalines = f.readlines() print(datalines)
我们知道用 "rb" 的方式表示读二进制文件(图片,音乐,视频等),但要注意,在 Python3 中读取的内容是 bytes 类型。
f = open("d:/test.png", "rb") # 要确保 d 盘下存在 test.png 文件,否则会报异常 data = f.read() print(type(data)) # <class 'bytes'> f.close() # 用完后关闭文件对象
最后要注意:我们打开文件,一定要掉用 close 函数关闭文件对象,否则进程会占用该文件对象的句柄,导致对文件本身的操作无效,比如删除该文件(大家可以做个测试)。
写文件和读文件打开使用的函数是一样的,唯一区别是调用 open 函数时,传入标识符 "w" 或者 "wb",其中标识符 "w" 表示写文本文件,"wb" 表示写二进制文件。
f = open("d:/test.txt", "w") f.write("birdpython") f.close() # 调用 close 函数关闭文件对象
我们可以反复调用 write 函数来写入文件,文件对象有一个指针会指向每一次写入内容的最后一个位置, 这样下一次要写入的内容会紧跟着上一次写入内容的末尾,当然我们也可以修改这个指针的指向。
f = open("d:/test.txt", "w") f.write("birdpython") f.write("byebye") # 紧跟着 "n" 后面的位置写入 "byebye" f.seek(1) f.write("haha") # 会从 "i" 位置开始往后写如 "haha" f.close()
对文件写操作执行完成后要调用 close 函数来关闭文件,当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用 close 函数时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用 close 的后果是数据可能只写了一部分到磁盘,或者没有写入。
f = open("d:/test.txt", "w") f.write("birdpython") input("waiting...") # 进程阻塞在此处,没有调用 close 函数且进程没有退出,操作系统并不会把 "birdpython" 写入磁盘 f.close()
但如果我们要间断性的写入很多内容,我们想每次执行 write 函数后,就把内容写入到磁盘中,如果每写一句 write函数,就调用一次 close 函数释放文件对象,我们下次写的话,还要重新申请文件对象,这样频繁的向操作系统申请和释放文件对象是很耽搁时间的,是一种很 low 的解决方案。 我们可以使用 flush 函数告诉操作系统不需要等待 close 函数,可以马上写入内容到磁盘。
f = open("d:/test.txt", "w") f.write(u"birdpython") f.flush() f.write(u"byebye") f.flush() f.write(u"hahaha") f.close()
我们每次使用标识符 "w" 写文件的方式返回一个文件对象,如果打开的文件存在,操作系统会删除该文件,然后重新写入, 这样会丢失已有的文件。
f = open("d:/test.txt", "w") f.write(u"birdpython") f.close() f = open("d:/test.txt", "w") # 操作系统会删除掉 test.txt 文件,然后重新创建 f.write(u"byebye") f.close() # 最后文件里的内容只有 "byebye"
我们可以使用标识符 "a",这样可以追加方式写文件,会在原有的文件内容后面写入我们的内容。
f = open("d:/test.txt", "w") # 操作系统会删除掉 test.txt 文件,然后重新创建 f.write(u"birdpython") f.close() f = open("d:/test.txt", "a") # 操作系统会在 test.txt 文件内容最后一个位置追加写 f.write(u"byebye") f.close()
读写文件还有标识符 "w+","r+","a+" 等等,大家用到时候可以自行 google 搜索。
注意:当我们以写方式通过文件对象打开一个文件,在未调用 close 函数之前,最好不要做对该文件内容的修改操作(比如写文件)或文件本身的修改操作(比如删除文件)。
像 open 函数返回的这种有个 read 函数的对象,在 Python 中统称为 file-like Object。除了 file ,还可以是内存的字节流,网络流,自定义流等等。file-like Object 不要求从特定类继承,只要写个 read 函数就行。
我们后面会学习各种文件对象,特别是在网络编程中,我们要学习的套接字也是文件对象,套接字的 read 函数是从网卡中读入字节流,write 函数是告诉操作系统把字节流写入到网卡,这样就可以和远程机器进行通信了。
文件对象不一定是必须要和磁盘,网卡这种外部设备打交道,和内存打交道的具有 read 和 write 函数的对象,我们也叫文件对象,下节我们会学习。
知道文件对象的定义。
读写文件的各种标识符的意义。
读写文件的注意事项。
思考一下为什么操作系统不允许程序直接访问磁盘。
写的超好!很感谢你的文章~
文件过大怎么读取,初学者不明白
python为啥没有像C语言那种的fread函数,直接读取二进制文件并根据数据类型自动转换相应的数据。比如说二进制文件中存了float类型的数据,用C语言的话,定义一个float数组就可以直接用fread读取出来了,但python还得各种转换
有pickle和json,往后看
file.seek(0) # 光标移动到文件开头 和file.seek(0,0) # 移动光标到文件开头 这两个括号里的0有啥区别吗
(a,b) 表示a行第b个字符
不错,一次没看完,mark下,回来继续看
写的很详细