python基础五(文件操作)
一 文件操作
一 介绍
计算机系统分为:计算机硬件,操作系统,应用程序三部分。
我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序是无法直接操作硬件的,这就用到了操作系统。操作系统把复杂的硬件操作封装成简单的接口给用户/应用程序使用,其中文件就是操作系统提供给应用程序来操作硬盘虚拟概念,用户或应用程序通过操作文件,可以将自己的数据永久保存下来。
有了文件的概念,我们无需再去考虑操作硬盘的细节,只需要关注操作文件的流程:
#1. 打开文件,得到文件句柄并赋值给一个变量 #2. 通过句柄对文件进行操作 #3. 关闭文件
1、什么是文件 文件是操作系统提供给用户/应用程序操作硬盘的一种虚拟的概念/接口 用户/应用程序(open()) 操作系统(文件) 计算机硬件(硬盘) 2、为何要用文件 用户/应用程序可以通过文件将数据永久保存到硬盘中 既操作文件就是操作硬盘 用户/应用程序直接操作的是文件,对文件进行的所有的操作,都是 在向操作系统发送系统调用,然后再由操纵将其转换成具体的硬盘操作 3、如何用文件:open() 控制文件读写内容的模式:t和b 强调:t和b不能单独使用,必须要跟r/w/a连用 # t文本(默认的模式) 1、读写都以str(unicode)为单位的 2、文本文件 3、必须指定encoding='utf-8' # b二进制/bytes # 控制文件读写操作的模式 # r只读模式 # w只写模式 # a只追加写模式 # +:r+、w+、a+
# 没有指定unccoding参数操作系统会使用自己默认的编码
# linux系统默认utf-8
# windows系统默认gbk
with open(r'a.txt', mode='rt', encoding='utf-8') as f:
res = f.read() # t模式会将f.read()读出的结果解码成unicode
print(f, type(f))
print(res)
# 内存:utf-8格式的二进制-----解码(decoding)----》unicode
# 硬盘(a.txt内容:utf-8格式的二进制)
# t模式方便了文本的读写,不然还有个unicode的编码和解码
二 在python中
#1. 打开文件,得到文件句柄并赋值给一个变量 f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r #2. 通过句柄对文件进行操作 data=f.read() #3. 关闭文件 f.close()
三 f=open('a.txt','r')的过程分析
#1、由应用程序向操作系统发起系统调用open(...) #2、操作系统打开该文件,并返回一个文件句柄给应用程序 #3、应用程序将文件句柄赋值给变量f
四 强调!!!
#强调第一点: 打开一个文件包含两部分资源:操作系统级打开的文件+应用程序的变量。在操作完毕一个文件时,必须把与该文件的这两部分资源一个不落地回收,回收方法为: 1、f.close() #回收操作系统级打开的文件 2、del f #回收应用程序级的变量 其中del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件还没有关闭,白白占用资源, 而python自动的垃圾回收机制决定了我们无需考虑del f,这就要求我们,在操作完毕文件后,一定要记住f.close() 虽然我这么说,但是很多同学还是会很不要脸地忘记f.close(),对于这些不长脑子的同学,我们推荐傻瓜式操作方式:使用with关键字来帮我们管理上下文 with open('a.txt','w') as f: pass with open('a.txt','r') as read_f,open('b.txt','w') as write_f: data=read_f.read() write_f.write(data)
#强调第二点: f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。 这就用到了上节课讲的字符编码的知识:若要保证不乱码,文件以什么方式存的,就要以什么方式打开。 f=open('a.txt','r',encoding='utf-8')
五 python2中的file与open
#首先在python3中操作文件只有一种选择,那就是open() #而在python2中则有两种方式:file()与open() 两者都能够打开文件,对文件进行操作,也具有相似的用法和参数,但是,这两种文件打开方式有本质的区别,file为文件类,用file()来打开文件,相当于这是在构造文件类,而用open()打开文件,是用python的内建函数来操作,我们一般使用open()打开文件进行操作,而用file当做一个类型,比如type(f) is file
二 打开文件的模式
# coding:gbk # 1、打开文件 # windows路径分隔符问题 # open('c:\a\liuqiao\d.txt') # 解决方案一:推荐 # open(r'c:\a\liuqiao\d.txt') # 解决方案二: # open('c:/a/liuqiao/d.txt') f = open(r'E:\Python学习\python全栈学习\day11\a.txt', mode='rt', encoding='utf-8') # f的值是一种变量,占用的是应用程序的内存空间 print(f) print(type(f)) # <_io.TextIOWrapper name='E:\\学习\\Python学习\\pycharm编程实践\\day11\\a.txt' mode='r' encoding='cp936'> # f是文本数据类型 # # 2、操作文件:读/写文件,应用程序对文件的读写请求都是在向操作系统发送 # 系统调用,然后由操作系统控制硬盘把输入读入内存、或者写入硬盘 res = f.read() print(res) # print(f.read()) # 3、关闭文件 # f.close() #回收操作系统资源 # print(f) #变量f存在,但是不能再读 # f.read()#变量f存在,但是不能再读
件句柄 = open('文件路径', '模式')
模式可以是以下方式以及他们之间的组合:
Character | Meaning |
‘r' | open for reading (default) |
‘w' | open for writing, truncating the file first |
‘a' | open for writing, appending to the end of the file if it exists |
‘b' | binary mode |
‘t' | text mode (default) |
‘+' | open a disk file for updating (reading and writing) |
‘U' | universal newline mode (for backwards compatibility; should not be used in new code) |
#1. 打开文件的模式有(默认为文本模式): r ,只读模式【默认模式,文件必须存在,不存在则抛出异常】 w,只写模式【不可读;不存在则创建;存在则清空内容】 a, 之追加写模式【不可读;不存在则创建;存在则只追加内容】 #2. 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式) rb wb ab 注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码 #3. 了解部分 "+" 表示可以同时读写某个文件 r+, 读写【可读,可写】 w+,写读【可读,可写】 a+, 写读【可读,可写】 x, 只写模式【不可读;不存在则创建,存在则报错】 x+ ,写读【可读,可写】 xb
# 回车与换行的来龙去脉 http://www.cnblogs.com/linhaifeng/articles/8477592.html # U模式 'U' mode is deprecated and will raise an exception in future versions of Python. It has no effect in Python 3. Use newline to control universal newlines mode. # 总结: 在python3中使用默认的newline=None即可,换行符无论何种平台统一用\n即可
三 操作文件的方法
以t模式为基础进行内存操作
# 1、r(默认的操作模式):只读模式,当文件不存在时报错,当文件存在时文件指针跳到开始位置 with open(r'a.txt', mode='rt', encoding='utf-8') as f: print('第一次读'.center(30, '*')) res = f.read() print(res) print('第二次读'.center(30, '*')) res1 = f.read() print(res1)
# 小练习:实现用户认证功能 # 方案一 inp_name = input('请输入你的名字: ').strip() inp_pwd = input('请输入你的密码: ').strip() with open(r'user.txt', mode='r', encoding='utf-8') as f: res = f.read() l1 = res.split() # 以\n为界切分成列表 print(l1) for l2 in l1: # 遍历列表,生成新的name和pwd的列表 u, p = l2.split(':') # 列表解压赋值 print(u, p) if inp_name == u and inp_pwd == p: print('登录成功') break else: print('账号名或者密码错误') # 方案二 inp_name = input('请输入你的名字: ').strip() inp_pwd = input('请输入你的密码: ').strip() with open(r'user.txt', mode='r', encoding='utf-8') as f: for line in f: # 生成的line是字符串(含了.read的功能),先.strip去除字符串前后的\n,再.split以':'为界切分成列表。 print(line, end='') # liuqiao:123\n print(line.strip('\n').split(':')) u, p = line.strip('\n').split(':') # 把用户输入的名字与密码与读出内容做比对 if inp_name == u and inp_pwd == p: print('登录成功') break else: print('账号名或者密码错误') # 应用程序---》文件 # 应用程序---》数据库管理软件----》文件
# 2、w:只写模式 ,当文件不存在时会创建空文件,当文件存在会清空文件,指针位于开始位置 with open('test.txt', mode='wt', encoding='utf-8') as f: f.write('x') f.write('lq') # 强调1 # 在以w模式打开文件没有关闭的情况下,连续写入,新的内容总是跟在旧的之后 with open('a.txt', mode='wt', encoding='utf-8') as f: f.write('I love you !\n') f.write('real\n') f.write('yes\n') # 强调2 # 如果重新以w模式打开文件,则会清空文件内容 # 案例:w模式用来创建全新的文件 # 文本文件的copy工具 with open('a.txt', mode='r', encoding='utf-8') as f1, \ open('c.txt', mode='w', encoding='utf-8') as f2: res = f1.read() f2.write(res) # 路径copy
# 3、a:只追加写,在文件不存在时会创建空文档,文件存在会将文件指针直接移动到文件末尾 with open('aaa.txt', mode='at', encoding='utf-8') as f: f.write('I love you !\n') f.write('real\n') f.write('yes\n') ''' #强调 w 模式与 a 模式的异同: # 1 相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前面写的内容之后 # 2 不同点:以 a 模式重新打开文件,不会清空原文件内容,会将文件指针直接移动到文件末尾,新写的内容永远写在最后 ''' # 案例:a模式用来在原有的文件内存的基础之上写入新的内容,比如记录日志、注册功能 # 注册功能 ipname = input('请输入账号:').strip() ippasword = input('请输入密码:').strip() with open('register.txt', mode='a', encoding='utf-8') as f: f.write('{}:{}\n'.format(ipname, ippasword))
# 4、了解:+不能单独使用,必须配合r、w、a with open('c.txt', mode='r+', encoding='utf-8') as f: f.read() f.write('南平\n') f.write('南平平\n') # w+,a+,.read()从指针位置开始读 # r+ w+ a+ :可读可写 # 在平时工作中,我们只单纯使用r/w/a,要么只读,要么只写,一般不用可读可写的模式
以b模式为基础进行内存操作
t:
1、读写都是以字符串(unicode)为单位
2、只能针对文本文件
3、必须指定字符编码,既必须指定encoding参数
b:binary模式
1、读写都是以bytes为单位
2、可以针对所有文件
3、一定不能指定字符编码,一定不要指定encoding参数
with open(r'C:\Users\qiao\Desktop\test1.jpg', mode='rb') as f: res = f.read() # 硬盘的二进制读入内存--->b模式,不做任何转换,直接读入内存 print(res) # bytes类型-->当成二进制 print(type(res)) with open(r'aa.txt', mode='rb') as f: res = f.read() # utf-8的二进制 print(res) print(type(res)) res1 = res.decode() # 解码,就是t模式 print(res1)
强调:b模式对比t模式
1、在操作纯文本文件方面t模式帮我们省去了编码与解码的环节,b模式则需要手动编码与解码,所以此时t模式更为方便
2、针对非文本文件(如图片、视频、音频等)只能使用b模式
# 例子b模式下的w模式,文本举例 with open(r'bb.txt', mode='wb') as f: res = f.write('小宝小小宝'.encode('utf-8'))
# 文件拷贝工具 src_file = input('源文件路径: ').strip() dst_file = input('目标文件路径: ').strip() with open(r'{}'.format(src_file), mode='rb') as read_f, open(r'{}'.format(dst_file), mode='wb') as write_f: for line in read_f: print(line) write_f.write(line)
循环读取文件:
# 方式一:自己控制每次读取的数据的数据量 with open(r'test1.jpg', mode='rb') as f: while True: res = f.read(1024) # 1024个字节为单位读取,数量是编写者为准 if len(res) == 0: break print(res) print(len(res))
# 方式二:以行为单位读,当一行内容过长时会导致一次性读入内容的数据量过大 with open(r'cc.txt', mode='rb') as f: for line in f: print(line) with open(r'test1.jpg', mode='rb') as f1: for line in f1: print(line) # bytes类型数据也可通过for循环一行一行的读出来,\n为一行
文件操作的其他方法
# 一、读操作 # 1、readline:一次读一行,读取一行内容,光标移动到第二行首部 with open(r'aa.txt', mode='rt', encoding='utf-8') as f: res = f.readline() print(res, end='') # end默认'\n' res1 = f.readline() print(res1, end='*') with open(r'aa.txt', mode='rt', encoding='utf-8') as f1: while True: res2 = f1.readline() if len(res2) == 0: break print(res2, end='') # 2、readlines:读取每一行内容,存放于列表中 with open(r'aa.txt', mode='rt', encoding='utf-8') as f: res = f.readlines() print(res) # end默认'\n' ['liuqiao\n', '刘巧\n', 'xiaobao'] # f.read()与f.readlines()都是将内容一次性读入内容,如果内容过大会导致内存溢出
# 二:写相关操作 # f.writeline(): with open(r'dd.txt', mode='wt', encoding='utf-8') as f: f.writelines(['小宝\n', 'xiaobao\n', '123']) with open(r'dd.txt', mode='rt', encoding='utf-8') as f1: res = f1.read() print(res, type(f1.read())) print(f1.read()) # print在with子代码下,打印不出得,已关闭文件了
with open(r'ee.txt', mode='wb') as f: # 补充1:如果是纯英文字符吗,可以直接加前缀b得到bytes类型 # l = [b'lq', b'xiaobao', b'123'] # 补充2:'上'.encode('utf-8')等同于bytes('上',encoding('utf-8') l = [bytes('小宝\n', encoding='utf-8'), bytes('xiaobao\n', encoding='utf-8'), b'xiaobao123'] f.writelines(l)
# flush:立刻将文件内容从内存刷到硬盘,因为操作系统会在内存存了一堆数据后,再写入硬盘,操作系统已优化,flush用来测试 with open(r'ff.txt', mode='wt', encoding='utf-8') as f: f.write('小宝123') f.flush()
# 三.了解 with open(r'hh.txt', mode='wt', encoding='utf-8') as f: print(f.readable()) print(f.writable()) print(f.encoding) print(f.closed) print(f.closed)
四、控制文件指针的移动
# 指针移动的单位都是一bytes字节为单位 # 只有有一种情况特殊: # t模式下的read(n),n代表的是字符个数 # f.seek(n,模式):n指的是移动的字节个数 # 模式: # 模式0:参照物是文件开头位置 # f.seek(9,0) # f.seek(3,0) # 3 with open(r'ff.txt', mode='rt', encoding='utf-8') as f: f.seek(3, 0) # 参照文件开头移动了3个字节 print(f.tell()) # 查看当前文件指针距离文件开头的位置,输出结果为3 print(f.read()) # 从第3个字节的位置读到文件末尾,输出结果为:宝123;read从指针位置开始,往后读 # 注意:由于在t模式下,会将读取的内容自动解码,所以必须保证读取的内容是一个完整中文数据,否则解码失败,一个中文是3个bytes with open('ff.txt', mode='rb') as f: f.seek(6, 0) print(f.tell()) print(f.read().decode('utf-8')) # 输出结果为:123
# 模式1:参照物是当前指针所在的位置 # f.seek(9,1) # f.seek(3,1) #12 with open(r'ff.txt', mode='rb') as f: f.seek(3, 1) # 从当前位置往后移动3个字节,而此时的当前位置就是文件开头 print(f.tell()) # 输出结果为:3 f.seek(4, 1) # 从当前位置往后移动4个字节,而此时的当前位置为3 print(f.tell()) # 输出结果为:7 # 模式2:参照物是文件末尾位置,应该倒着移动 # f.seek(9,2) #末尾了,不能在向右移动,还是末尾位置 # f.seek(-9,2) #应该倒着移动 with open(r'ff.txt', mode='rb') as f: f.seek(0, 2) # 参照文件末尾移动0个字节, 即直接跳到文件末尾 print(f.tell()) # 输出结果为:9 f.seek(-3, 2) # 参照文件末尾往前移动了3个字节 print(f.read().decode('utf-8')) # 输出结果为:123
自己总结文件内容b模式下:f.write('xxx'.encode('utf-8'));f.read().decode('utf-8'),写是编码,读是解码。
# 之前文件内指针的移动都是由读/写操作而被动触发的,若想读取文件某一特定位置的数据,则则需要用f.seek方法主动控制文件内指针的移动,详细用法如下:
# f.seek(指针移动的字节数,模式控制):
# 模式控制:
# 0: 默认的模式,该模式代表指针移动的字节数是以文件开头为参照的
# 1: 该模式代表指针移动的字节数是以当前所在的位置为参照的
# 2: 该模式代表指针移动的字节数是以文件末尾的位置为参照的
# 强调:其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用
# f.tell() #获取文件指针当前位置
五、文件修改的两种方式
# 强调: # 1、硬盘空间是无法修改的,硬盘中数据的更新都是用新内容覆盖旧内容 # 2、内存中的数据是可以修改的 # 1 文件修改方式一 # 实现思路:将文件内容发一次性全部读入内存,然后在内存中修改完毕后再覆盖写回原文件 # 优点: 在文件修改过程中同一份数据只有一份 # 缺点: 会过多地占用内存 with open('aaa.txt', mode='rt', encoding='utf-8') as f: data = f.read() with open('aaa.txt', mode='wt', encoding='utf-8') as f: f.write(data.replace('xiaobao', 'zzzzdddd')) # 2 文件修改方式二 # 实现思路:以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件...,删掉原文件,将临时文件重命名原文件名 # 优点: 不会占用过多的内存 # 缺点: 在文件修改过程中同一份数据存了两份 import os with open('aaa.txt', mode='rt', encoding='utf-8') as read_f, \ open('.db.txt.swap', mode='wt', encoding='utf-8') as wrife_f: for line in read_f: wrife_f.write(line.replace('zzzzdddd', 'xiaobao')) os.remove('aaa.txt') os.rename('.db.txt.swap', 'aaa.txt')
六、作业
#作业二:请闭眼写出购物车程序 #需求: 注册用户名和密码存放于文件中,格式为:egon,123,10000并输入工资收入 启动程序后,先登录,登录成功,然后打印商品列表,失败则重新登录,超过三次则退出程序 允许用户根据商品编号购买商品 用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒 可随时退出,退出时,打印已购买商品和余额
product_list = [['Iphone7', 5800], ['Coffee', 30], ['疙瘩汤', 10], ['Python Book', 99], ['Bike', 199], ['ViVo X9', 2499], ] shopping_cart = {} db_file = r'db.txt' current_user_info = [] while True: print(''' 1、登录 2、注册 3、购物 ''') choice = input('请输入选择:').strip() # 登录 if choice == '1': count = 0 tag = True while tag: if count == 3: print('输入错误超过三次,请重新登录!') break username = input('请输入用户名:').strip() password = input('请输入密码:').strip() with open(db_file, mode='rt', encoding='utf-8') as f: for line in f: line = line.strip('\n') user_info = line.split(',') if username == user_info[0] and password == user_info[1]: print('登录成功') current_user_info = [user_info[0], user_info[2]] print('用户信息:', current_user_info) tag = False break else: print('账号或密码错误') count += 1 # 注册 if choice == '2': reg_username = input('请输入注册用户名:') while True: reg_password = input('请输入注册用密码:') reg_password1 = input('再次输入注册用户密码:') if reg_password == reg_password1: break else: print('两次密码不一致,请重新注册!') income = input('请输入工资收入:') with open(db_file, mode='at', encoding='utf-8') as f: f.write('\n{},{},{}'.format(reg_username, reg_password, income)) # 购物 if choice == '3': if len(current_user_info) == 0: print('请先登录!') else: # 登录成功,请购物 print('先生{}您好,你的余额还有{}元,请尽情购物!!!'.format(current_user_info[0], current_user_info[1])) uname_of_db = current_user_info[0] income_db = current_user_info[1] tag = True while tag: for index, product in enumerate(product_list): print(index, product) choice = input('请输入商品编号,输入q退出:').strip() if choice.isdigit(): choice = int(choice) if choice < 0 or choice > len(product_list): continue p_name = product_list[choice][0] p_price = product_list[choice][1] income_db = int(current_user_info[1]) if p_price < income_db: if p_name in shopping_cart: # 以前买过 shopping_cart[p_name]['count'] += 1 else: shopping_cart[p_name] = {'p_price': p_price, 'count': 1} income_db -= p_price current_user_info[1] = income_db print('增加了商品+{}+到了购物车里'.format(p_name)) else: print('穷逼你买不起,商品价格是{}元,你还差{}元!'.format(p_price, p_price - income_db)) print(shopping_cart) elif choice == 'q': print(''' ------------------- 已购买商品列表 ------------------- id 商品 数量 单价 总价 ''') total_cost = 0 for i, key in enumerate(shopping_cart): print('%22s%18s%18s%18s%18s' % ( i, key, shopping_cart[key]['count'], shopping_cart[key]['p_price'], shopping_cart[key]['p_price'] * shopping_cart[key]['count'] )) total_cost += shopping_cart[key]['p_price'] * shopping_cart[key]['count'] print(""" 您的总花费为: %s 您的余额为: %s ---------------------------------end--------------------------------- """ % (total_cost, income_db)) while tag: inp = input('确认购买(yes/no?)>>: ').strip() if inp not in ['Y', 'N', 'y', 'n', 'yes', 'no']: continue if inp in ['Y', 'y', 'yes']: # 将余额写入文件 import os with open(db_file, mode='rt', encoding='utf-8') as f_read, \ open(r'swp.txt', mode='wt', encoding='utf-8') as f_write: for line in f_read: if line.startswith(current_user_info[0]): l = line.strip('\n').split(',') l[2] = str(income_db) line = ','.join(l) + '\n' f_write.write(line) os.remove(db_file) os.rename(r'swp.txt', db_file) print('购买成功') print(current_user_info) shopping_cart = {} current_user_info = [] tag = False else: print('输入有问题') else: print('输入非法')