文件操作
昨日回顾
1. 基本数据类型补充 join() 把传递进去的参数进行迭代然后和前面的字符串进行拼接, 返回字符串 split() 把字符串切割成列表 关于删除: 列表在循环的时候不能直接删除. 因为索引会发生改变. 把要删除的内容保存在新列表中, 然后循环这个新列表. 删除老列表 字典在循环的时候也不能直接删除. 如果删除会报错 fromkeys() 创建字典 坑1: 返回新字典, 对原来的字典没有影响 坑2: 如果给的value是可变的数据类型. 多个key共享同一个value. 2. 深浅拷贝 1. = 没有创建新的对象. 2. lst.copy() lst[:] 浅拷贝, 拷贝第一层内容 3. import copy copy.deepcopy() 深拷贝. 把列表内的所有内容进行拷贝和复制
今日内容
1. 文件操作 open 打开 f = open(文件路径, mode="模式", encoding="编码格式") 最最底层操作的就是bytes 打开一个文件的时候获取到的是一个文件句柄.##f是文件句柄,相当于吸管/锅把(只是个媒介而已),而不是文件本身 绝对路径 从磁盘根目录开始寻找 相对路径 相对于当前程序所在的文件夹 ../ 上一层文件 文件夹/ 进入xxx文件夹 2. mode:
注意:读的好习惯是f.close()
写的好习惯是f.flush() f.close()
r: 读取,只读. 读取文件的相关操作 1. read() 默认: 读取文件内容(全部) read(n) 读取n个字符(##不管是中文还是英文,还是读出来的字节,都当成是一个字符) 2. readline() 读取一行 3. readlines() 读取全部. 返回列表 (最重要)4. for line in f: 每次读取一行内容 w: 写入. 只写 创建文件 会清空文件(##覆盖写) a: 也可以创建文件 追加写 r+: 对于文件而言. 应该有的操作就两个:读, 写 读写操作 w+: 写读操作 a+: 追加写读 所有带b的表示直接操作的是bytes, 当处理非文本文件的时候. rb wb ab: 断点续传 r+b w+b a+b 3. 文件操作中关于文件句柄的相关操作 seek() 移动光标 f.seek(0) 移动到开头(最多) ##等价于seek(0,0),从开头位置(第二个参数0)偏移0个字节(第一个参数0) f.seek(0, 2) 移动到末尾 ##从末尾位置偏移0个字节 seek:两个参数 1. 表示偏移量 2. 从xxx开始偏移, 默认0开头 1当前位置 2末尾 tell 返回光标所在的位置
truncate(n), 如果给出了n. 则从开头开头进⾏截断, 如果不给n, 则从当前位置截断. 后⾯的内容将会被删除
有n,留下的是从文件的开头到n个字节 没有n,留下的是从文件的开头到当前位置所在的字节
4.实际操作(重点)
文件复制
文件修改
水果统计
一.初识文件操作
# r: read 只读 # f是文件句柄,相当于吸管/锅把,而不是文件本身 # 这里的 encoding="utf-8" 包含一个decode的过程,具体的不太清晰??? f = open("alex和wusir的故事", mode="r", encoding="utf-8") # 读取内容 content = f.read() # 全部都读取出来 print(content) # 坑: c2 = f.read() # 读取不到内容. 因为上面已经读取完毕. 光标在末尾 print("c2", c2) # 良好的习惯-->read的 f.close() # 关闭连接
# 文件路径: # 1. 相对路径: 相对于当前程序所在的文件夹, 如果在文件夹内,直接写名字 # 如果不在这个文件夹内,可能需要出文件夹或者进文件夹 # 出文件夹 ../ (出文件夹不需要标注出出来哪个文件夹,直接../就是走出了该文件当前所在的文件夹) # 进文件夹 要进的文件夹的名字/ # 2. 绝对路径: 从磁盘根目录寻找路径 (有问题:代码传输之后,如果文件更改了路径就要相应的更改代码里面的路径) # 只有在记录日志的时候可以用到绝对路径(因为日志信息一般只会读,不会去更改) # 日志: 程序运行过程中记录的信息. # 'utf-8' codec can't decode byte 0xbd in position 0: invalid start byte f = open("E:\西游记\金角大王吧.txt", mode="r", encoding="gbk") # 直接在win电脑上创建的文本文件是使用的'gbk'编码的 print(f.read()) f.close()
使用python来读写文件是非常简单的操作.我们使用open0函数来打开一个文件,获取到文件句柄.然后通过文件句柄就可以进行各种各样的操作了.根据打开方式的不同能够执行的操作也会有相应的差异.
打开文件的方式:r, w, a, r+, W+, a+, rb, wb, ab, r+b, w+b, a+b默认使用的是r(只读)模式
绝对路径和相对路径:
1.绝对路径:从磁盘根目录开始一直到文件名.
2.相对路径:同-个文件夹下的文件.相对于当前这个程序所在的文件夹而言.如果在同
一个文件夹中则相对路径就是这个文件名.如果在上- -层文件夹则要../
我们更推荐大家使用相对路径.因为在我们把程序拷贝给别人使用的时候.直接把项目拷贝走
就能运行.但是如果用绝对路径.那还需要拷贝外部的文件.
二.mode
只读(r, rb)
f = open("小护士模特主妇萝莉.txt", mode="r", encoding="utf-8") print(f.read(5)) # 读取5个字符 ##不管是中文还是英文 print(type(f.read(5))) # <class 'str'> print(f.read(5)) # 继续读5个 print(f.readline().strip()) # 读取一行的内容,以换行符作为分割符, 用strip()可以去掉换行. 读取到的内容第一件事就是去掉空白 print(f.readline()) ## 打印的结果会两次换行(即行与行之间会间隔一个空行).因为readline()读取的内容中末尾是\n会换行,而print方法的参数默认end = '\n'末尾会有一个换行效果 # 去掉多余的换行的方法 print(f.readline().strip()) #去掉readline()里面的'\n' print(f.readline(), end='') #去掉print()里面的'\n' # 良好的习惯-->read的 f.close() print("周润发", end="胡辣汤") # print里面的end参数可以随意自定义修改 print("周星驰") print(f.readlines()) # 一次性把文件中的内容读取到列表中.##每个元素是文件一行的内容和\n,最后一行可能没有换行符,就看当初写的时候有没有写进去了.(换行符在文件中是直接行使作用的,是看不到以字符'\n'的样子显示出来的,文件会自动将它的效果显示出来-->对每一行的文件内容进行换行;而将文件中的内容读取出来的时候,相当于将文件还原成当初的代码) #代码中的'\n'<--->文件中的换行效果 是对应的 代码是给机器看的,文件是给我们看的. print("你叫什么名字?\n 我叫李嘉诚. 可能") # 换行 print("你叫什么名字?\\n 我叫李嘉诚. 可能") # 显示 \n(\用来转义) print("你叫什么名字?\\\\n 我叫李嘉诚. 可能") # 显示 \\n # 文件句柄是一个可迭代对象 ##迭代出来的是文件每一行的内容 # 优点: 相对来说节省内存, 操作相对简单 for line in f: # 从文件中读取到每一行给前面的line print(line.strip()) f.close() import pickle print(pickle.dump('我爱我家', open('aaa','wb'))) # 使用pickle序列化之后得到字节码再写入文件 f = open("aaa", mode="rb") print(f.read(5)) # b'\x80\x03X\x0c\x00' 读取5个字符 ##不管是中文还是英文,还是读出来的字节,都当成是一个字符 print(len(f.read(5))) # 5 print(type(f.read(5))) # <class 'bytes'> """ f.read(5)#读取的是字符,但是所谓的字符是 不管是中文还是英文,还是读出来的字节,都当成是一个字符 """
f = open("护士少妇嫩模. txt" , mode="r", encoding= "utf-8") content = f. read() print(content) f.close()
需要注意encoding表示编码集.根据文件的实际保存编码进行获取数据,对于我们而言.更多的是utf-8.
rb.读取出来的数据是bytes类型,在rb模式下.不能选择encoding字符集.
f = open("护士少妇嫩模. txt" , mode="rb" ) content = f . read() print(content) f.close() 结果: b'\xe6\xaf\x85\xe5\x93\xa5, \xe5\xa4\xaa\xe7\x99\xbd, wuse\n\xe5\x91\xb5\xe5\x91\xb5\n\xe6\x97\xa5\xe5\xa4\xa9'
rb的作用:在读取非文本文件的时候.比如读取MP3.图像.视频等信息的时候就需要用到rb.因为这种数据是没办法直接显示出来的.在后面我们文件上传下载的时候还会用到.还有我们看的直播.实际上都是这种数据.
读取文件的方法:
1. read( 将文件中的内容全部读取出来.弊端:占内存.如果文件过大容易导致内存崩溃
f = open("../def/哇擦. txt", mode="r", encoding="utf-8") content = f. read() print(content) 结果: 友谊地久天长, 爱一点, 可惜我是水瓶座 一生中最爱
2. read(n) 读取n个字符.需要注意的是.如果再次读取.那么会在当前位置继续去读而不
是从头读,如果使用的是b模式.则读取出来的是n个字节
f = open(". ./def/哇擦. txt", mode=" r” encoding= "utf-8") content = f. read(3) print(content) #结果: 友谊地 f = open("../def/哇擦.txt", mode="rb") content = f.read(3) print(content) #结果: b'\xe5\x8f\x8b' f = open("../def/哇擦.txt", mode="r", encoding="utf-8") content = f.read(3) content2 = f.read(3) print(content) print(content2) #结果: 友谊地 久天⻓长
3. readline() 一次读取一⾏数据, 注意: readline()结尾, 注意每次读取出来的数据都会有一 个\n 所以呢. 需要我们使用strip()方法来去掉\n或者空格
f = open("../def/哇擦.txt", mode="r", encoding="utf-8") content = f.readline() content2 = f.readline() content3 = f.readline() content4 = f.readline() content5 = f.readline() content6 = f.readline() print(content) print(content2) print(content3) print(content4) print(content5) print(content6) 结果: 友谊地久天⻓长, 爱⼀一点, 可惜我是⽔水瓶座 ⼀一⽣生中最爱
4. readlines()将每一行形成一个元素,放到一个列表中.将所有的内容都读取出来.所以也是容易出现内存崩溃的问题.不推荐使⽤
f = open("../def/哇擦.txt", mode="r", encoding="utf-8") lst = f.readlines() print(lst) for line in lst: print(line.strip())
5. 循环读取.这种方式是组好的.每次读取一⾏内容.不会产生内存溢出的问题.
f = open("../def/哇擦.txt", mode="r", encoding="utf-8")
for line in f: print(line.strip())
注意: 读取完的文件句柄一定要关闭 f.close()
只写(w, wb)
# 每次用w模式打开文件, 都会清空这个文件(坑) f = open('胡辣汤', mode="w", encoding="utf-8") # 可以帮我们创建文件 f.write('河南特色\n') f.write("东北特色\n") f.write('陕西特色\n') # 好习惯 --> write的 f.flush() # 刷新管道(##管道:文件句柄f), 把数据写入文件 f.close() print('我爱我家'.encode('utf-8'))
写的时候注意. 如果没有文件, 则会创建文件,;如果文件存在.,则将文件中原来的内容删除, 再写入新内容
f = open("⼩小娃娃", mode="w", encoding="utf-8") f.write("⾦金金⽑毛狮王") f.flush() # 刷新. 养成好习惯 f.close()
尝试读一读
f = open("⼩小娃娃", mode="w", encoding="utf-8") f.write("⾦金金⽑毛狮王") #f.read() # 报错not readable 模式是w. 不可以执行读操作 f.flush() f.close()
wb模式下. 可以不指定打开⽂文件的编码. 但是在写⽂文件的时候必须将字符串串转化成utf-8的 bytes数据
f = open("⼩小娃娃", mode="wb") f.write("⾦金金⽑毛狮王".encode("utf-8"))
f.flush() f.close()
追加(a, ab)
f = open("葫芦小金刚", mode="a", encoding="utf-8") # a, append 追加, 在文件的末尾写入内容 f.write("你叫什么名字啊?") # f.read() # 报错not readable 因为追加是写的模式,不能读 f.flush() f.close()
在追加模式下. 我们写入的内容会追加在文件的结尾.
f = open("⼩小娃娃", mode="a", encoding="utf-8") f.write("麻花藤的最爱") f.flush() f.close()
mode_+
f = open("葫芦小金刚", mode="r+", encoding="utf-8") content = f.read(2) # 顺序必须先读, 后写 ##上来就先写也不会报错,但是开始的光标是在文件的开头,你写进去几个字符,原有的文件就会被相应的覆盖掉几个字符 # r+特有的深坑:不论读取内容的多少. 只要你读了. 写就是在末尾 f.write('五娃') print(content) # 一上来会清空文件. 没人用 f = open("葫芦小金刚", mode="w+", encoding="utf-8") f.write("又能吐火的, 有能吐水的.") print(f.tell()) # 33 返回光标的位置.写完光标就在末尾了,不移动光标就读是读不到内容的 # 移动光标 f.seek(0) # 移动到开头 s = f.read() print("=========>", s) # 追加写读, 光标在末尾. 所有的写都是在末尾 f = open("葫芦小金刚", mode="a+", encoding="utf-8") f.write("机器葫芦娃召唤神龙, 高喊.我代表月亮消灭你!") f.seek(0) s = f.read() print("=========>", s)
r+读写
对于读写模式. 必须是先读. 因为默认光标是在开头的. 准备读取的. 当读完了之后再进⾏写入. 我们以后使⽤频率最高的模式就是r+
正确操作是:(先读后写)
f = open("⼩小娃娃", mode="r+", encoding="utf-8") content = f.read() f.write("麻花藤的最爱") print(content) f.flush() f.close() #结果: 正常的读取之后, 写在结尾
错误操作:(先写后读)
f = open("⼩小娃娃", mode="r+", encoding="utf-8") f.write("哈哈") content = f.read() print(content) f.flush() f.close() #结果: 将开头的内容改写成了"哈哈", 然后读取的内容是后面的内容.(写入的内容把原本所在的位置覆盖了)
所以记住: r+模式下. 必须是先读取. 然后再写入
w+写读
先将所有的内容清空. 然后写入. 最后读取. 但是读取的内容是空的, 不常⽤
f = open("⼩小娃娃", mode="w+", encoding="utf-8") f.write("哈哈") content = f.read() print(content) f.flush() f.close()
有人会说. 先读不就好了么? 错. w+ 模式下, 一开始读取不到数据. 然后写的时候再将原来的内容清空. 所以, 很少用.
a+写读[追加写读)
a+模式下, 不论先读还是后读. 都是读取不到数据的.(因为打开文件光标就在末尾,写完之后还是在末尾,必须要移动光标才能读取到数据)
f = open("⼩小娃娃", mode="a+", encoding="utf-8") f.write("⻢马化腾") content = f.read() print(content) f.flush() f.close()
还有一些其他的带b的操作. 就不多赘述了. 就是把字符换成字节. 仅此⽽已
其他操作方法
f = open("胡辣汤", mode="r+", encoding="utf-8") f.seek(0,2) # 移动到末尾 content = f.read(5) #以字符为单位 print(content) f.seek(0) # 移动到开头 print(f.read()) # 以字节为单位 print(f.tell()) # 以字节为单位 f.seek(3) print(f.read())
1. seek(n)
光标移动到n位置, 注意, 移动的单位是byte. 所以如果是UTF-8的中文部分要是3的倍数.通常我们使用seek都是移动到开头或者结尾.
移动到开头: seek(0)
移动到结尾: seek(0,2) seek的第二个参数表示的是从哪个位置进⾏偏移, 默认是0, 表示开头, 1表示当前位置, 2表示结尾
即
seek(0,0)文件开头
seek(0,1)文件当前位置
seek(0,2)文件末尾
f = open("⼩小娃娃", mode="r+", encoding="utf-8") f.seek(0) # 光标移动到开头 content = f.read() # 读取内容, 此时光标移动到结尾 print(content) f.seek(0) # 再次将光标移动到开头 f.seek(0, 2) # 将光标移动到结尾 content2 = f.read() # 读取内容. 什什么都没有
print(content2) f.seek(0) # 移动到开头 f.write("张国荣") # 写⼊入信息. 此时光标在9 中⽂文3 * 3个 = 9
f.flush() f.close()
2. tell()
使用tell()可以帮我们获取到当前光标在什么位置
f = open("⼩小娃娃", mode="r+", encoding="utf-8") f.seek(0) # 光标移动到开头 content = f.read() # 读取内容, 此时光标移动到结尾 print(content) f.seek(0) # 再次将光标移动到开头
f.seek(0, 2) # 将光标移动到结尾 content2 = f.read() # 读取内容. 什什么都没有
print(content2) f.seek(0) # 移动到开头 f.write("张国荣") # 写⼊入信息. 此时光标在9 中⽂文3 * 3个 = 9 print(f.tell()) # 光标位置9 f.flush() f.close()
3. truncate()截断文件
f = open("⼩小娃娃", mode="w", encoding="utf-8") f.write("哈哈") # 写⼊两个字符 f.seek(3) # 光标移动到3, 也就是两个字中间
f.truncate() # 删掉光标后面的所有内容
f.close() f = open("⼩小娃娃", mode="r+", encoding="utf-8") content = f.read(3) # 读取12个字符 f.seek(4) print(f.tell()) f.truncate() # print(content) f.flush() f.close()
深坑请注意: 在r+模式下. 如果读取了内容. 不论读取内容多少. 光标显示的是多少. 再写入或者操作文件的时候都是在结尾进⾏的操作.
所以如果想做截断操作. 记住了. 要先挪动光标. 挪动到你想要截断的位置. 然后再进⾏截断.
关于truncate(n), 如果给出了n. 则从开头开头进⾏截断, 如果不给n, 则从当前位置截断. 后⾯的内容将会被删除
有n,留下的是从文件的开头到n个字节
没有n,留下的是从文件的开头到当前位置所在的字节
栗子:
f = open("a1.txt", mode="r+", encoding="utf-8") f.readline() print(f.truncate())# 没有截断的效果,原文件不变 f.seek(18) # 涉及到byte才截断 print(f.truncate())# 截断到18 ## 下面是我自己搜的 f=open('woo.txt','r+') print(f.readline()) f.tell() # 9读取后位置 f.truncate() # 返回值34,整个文件 f.seek(9) # 定位到9 ## 涉及到byte才截断 f.truncate() # 返回值9,已截断,所以truncate如果不传参数,之前需要先调一次seek定位(只是在r+模式下) # 因此猜测python底层是这么写的 def _truncate(self): # 真实的truncate # 函数内容省略 pass def truncate(self, size=None): # 你用的truncate if size: # 不传size需要手动调用一次seek self.seek(size) self._truncate()
应用
文件复制_bytes
f1 = open("c:/huyifei.jpg", mode="rb") f2 = open("d:/huerfei.jpg", mode="wb") for line in f1: # line是从f1中读取的内容 f2.write(line) # 把读取的内容原封不动的写出去 f1.close() f2.flush() f2.close() # 文件复制操作流程 # 1.拿到文件1的句柄以读模式打开,拿到文件2的句柄以写的模式打开 # 2.遍历(对文件的遍历是拿到每行的文件内容)文件1写入文件2中
文件的修改以及另-种打开文件句柄的方式
# 需求: 对文件'夸一夸alex' 把好人换成sb # 必须:(文件修改的操作步骤) # 1. 先从文件中读取内容-->(遍历文件) # 2. 把要修改的内容进行修改-->(replace替换,并赋值,拿到修改好的内容) # 3. 把修改好的内容写入一个新文件 # 4. 删除掉原来的文件 --> os.remove(老文件名) # 5. 把新文件重命名成原来的文件的名字 # 导入os模块 os表示操作系统 import os # f = open("夸一夸alex", mode="r", encoding="utf-8") # f2 = open("夸一夸alex_副本", mode="w", encoding="utf-8") # with会自动的帮我们关闭文件的链接 with open("夸一夸alex", mode="r", encoding="utf-8") as f, \ open("夸一夸alex_副本", mode="w", encoding="utf-8") as f2: for line in f: # 1.遍历文件 if "好人" in line: line = line.replace("好人", "sb") # 2.replace替换,并赋值,拿到修改好的内容 f2.write(line) # 3. 把修改好的内容写入一个新文件(存在,写入修改后的;不存在,写入原来的) # time.sleep(3) # 程序暂停3秒 # 4.删除原来文件 os.remove("夸一夸alex") # 5.重命名副本为原来的文件名 os.rename("夸一夸alex_副本", "夸一夸alex")
文件修改: 只能将文件中的内容读取到内存中, 将信息修改完毕, 然后将源文件删除, 将新文件的名字改成老文件的名字.
# 文件修改 import os with open("⼩小娃娃", mode="r", encoding="utf-8") as f1,\ open("⼩小娃娃_new", mode="w", encoding="UTF-8") as f2: content = f1.read() new_content = content.replace("冰糖葫芦", "⼤大⽩白梨梨") f2.write(new_content) os.remove("⼩小娃娃") # 删除源⽂文件 os.rename("⼩小娃娃_new", "⼩小娃娃") # 重命名新⽂文件
弊端: 一次将所有内容进行读取. 内存溢出.
解决⽅案: 一⾏一行的读取和操作
import os with open("⼩小娃娃", mode="r", encoding="utf-8") as f1,\ open("⼩小娃娃_new", mode="w", encoding="UTF-8") as f2: for line in f1: new_line = line.replace("⼤大⽩白梨梨", "冰糖葫芦") f2.write(new_line) os.remove("⼩小娃娃") # 删除源⽂文件 os.rename("⼩小娃娃_new", "⼩小娃娃") # 重命名新⽂文件
水果统计
文件 水果.txt 内容如下:
编号,名称,价格,数量
1,香蕉,1.85,50
2,苹果,2.6,67
3,榴莲,25,78
4,木瓜,3.5,89
# 文件的实际操作 # 需求:将文件'水果.txt' 转化成下面的列表中嵌套字典的样式,其中的key:编号,名称,价格,数量 均是文件第一行的的内容 # [{'编号': '1', '名称': '香蕉', '价格': '1.85', '数量': '50'}, # {'编号': '2', '名称': '苹果', '价格': '2.6', '数量': '67'}, # {'编号': '3', '名称': '榴莲', '价格': '25', '数量': '78'}, # {'编号': '4', '名称': '木瓜', '价格': '3.5', '数量': '89'}] # 方法一 ##缺点:这个的key是自定义写死的 f = open("水果.txt", mode="r", encoding="utf-8") lst = [] for line in f: # 1,香蕉,1.85,50 dic = {} # 每行都是一个字典 line = line.strip() # 去掉空白 \n 1,香蕉,1.85,50 a, b, c, d = line.split(",") # [1, 香蕉, 1.85, 50] dic['id'] = a dic['name'] = b dic['price'] = c dic['totle'] = d lst.append(dic) print(lst) # 方法二 ##缺点:这个每种水果的属性不便于修改(比如:增加个属性'总价',或者删除个属性'编号',都要相应的去修改原来的代码,主要是由于解构时候变量的数量给写死了) f = open("水果.txt", mode="r", encoding="utf-8") line = f.readline().strip() # 第一行内容, 编号,名称,价格,数量,哈哈 h,i,j,k = line.split(",") lst = [] for line in f: # 1,香蕉,1.85,50 dic = {} # 每行都是一个字典 line = line.strip() # 去掉空白 \n 1,香蕉,1.85,50 a, b, c, d = line.split(",") # [1, 香蕉, 1.85, 50] dic[h] = a dic[i] = b dic[j] = c dic[k] = d lst.append(dic) print(lst) # 方法三 ##这个使用遍历,完美的解决了上面没办法动态修改属性数量的问题 f = open("水果.txt", mode="r", encoding="utf-8") line1 = f.readline().strip() # 第一行内容, 编号,名称,价格,数量,哈哈 title = line1.split(",") # [编号,名称,价格,数量] lst = [] # i = 0 for line in f: # 1,香蕉,1.85,50 dic = {} # 每行都是一个字典 读到每一行都要初始化一个空字典 line = line.strip() # 去掉空白 \n 1,香蕉,1.85,50 data = line.split(",") # [1, 香蕉, 1.85, 50] for i in range(len(title)): # #遍历列表title和data都可以,因为它们的长度相同,索引相同 dic[title[i]] = data[i] lst.append(dic) print(lst)