Python-文件处理
零、基本概念
-
什么是文件
文件是操作系统提供给用户或应用程序操作硬盘的一种虚拟的接口。
-
为什么要用文件
用户/应用程序直接操作的是文件,对文件进行的所有操作,都是在向操作系统发送系统调用,然后再由操作系统将其转换成具体的硬盘操作。用户/应用程序可以通过文件将数据永久保存到硬盘中,或者从硬盘中读取数据,即操作文件就是操作硬盘。
-
如何使用文件:open()
f = open(),获得文件对象(文件句柄)
open()的参数最基本要有文件路径和操作模式。
一、模式介绍
-
控制文件读写内容的模式
控制文件读写内容的模式:t和b,且t和b不能单独使用,必须跟r/w/a连用。
- t-文本模式(默认的模式)
- 读写都是以str(实质在内存中是unicode)为单位的
- 操作只能是针对文本文件
- 必须指定字符编码,即必须指定encoding 参数
- b-binary模式(二进制/bytes)
- 读写都是以bytes为单位
- 可以针对所有文件
- 一定不能指定字符编码,即一定不能指定encoding 参数
- t-文本模式(默认的模式)
-
控制文件读写操作的模式
- r 只读模式
- w 只写模式
- a 只追加写模式
- +:r+、w+、a+
二、基本操作流程
-
打开文件(绝对路径/相对路径)
# windows路径分隔符问题 open('C:\a.txt\nb\c\d.txt') # 但因为有转义字符的存在,这么写有时候会给我们造成一些困扰。 # 解决方案一:前面加'r'(推荐) open(r'C:\a.txt\nb\c\d.txt') # 解决方案二:使用'/' open('C:/a.txt/nb/c/d.txt') f = open(r'aaa/a.txt',mode='rt') # f的值是一种变量,也被称为“文件对象”。 # 变量占用的都是应用程序的内存空间。
-
读/写文件:应用程序对文件的读写请求都是在向操作系统发送系统调用,然后由操作系统控制硬盘把数据读入内存、或者写入硬盘
res = f.read() print(type(res)) # <class 'str'>
-
关闭文件
f.close() # 回收操作系统资源,因为python不能自动回收操作系统资源 # f.read() # 变量f存在,但是不能再读了(报错),后续f被垃圾回收机制回收
三、资源回收与with语法
使用with语法,在执行完子代码块后,相当于自动在末尾增加执行close()程序,不用再手动进行回收。
with open('a.txt', mode='rt') as f1: # f1=open('a.txt',mode='rt')
res = f1.read()
print(res)
# 可以用with同时打开多个文件,用逗号分隔开即可
with open('a.txt',mode='rt') as f1,\
open('b.txt',mode='rt') as f2:
res1=f1.read()
res2=f2.read()
print(res1)
print(res2)
# 自动增加执行f1.close()
# 自动增加执行f2.close()
# '\ + 换行(回车)'可在代码过长时将代码分割开,在语法上还是在同一行。如果不加'\'进行转义就会有语法错误。
四、详细介绍文件模式
t文本(默认的文件模式)
- 读写都以str(unicode)为单位的
- 纯文本文件
- 必须指定encoding = ’ 字符编码 '(若要保证不乱码,文件以什么字符编码存的,就要以什么字符编码打开,读写也都将按照这个字符编码),没有指定encoding参数操作系统则会使用自己默认的编码。
- linux系统默认utf-8
- windows系统默认gbk
with open('c.txt', mode='rt', encoding='utf-8') as f:
res = f.read() # 将二进制数据从硬盘读出,t模式会将f.read()读出的结果解码成unicode
# 是一个解码过程,所以需要指定encoding
print(res, type(res))
# 内存中:utf-8格式的二进制-----解码-----》unicode
# 硬盘中(c.txt内容:utf-8格式的二进制)
r操作模式(默认的操作模式):只读模式,当文件不存在时报错,当文件存在时文件指针跳到最开始位置。
with open('c.txt',mode='rt',encoding='utf-8') as f:
res = f.read() # 把所有内容从硬盘读入内存,指针从头到尾
print(res)
# == == == == == == == =案例 == == == == == == == == ==
# user.txt文件内容的数据格式为“用户名:密码”
inp_username = input('your name>>: ').strip()
inp_password = input('your password>>: ').strip()
# 验证
with open('user.txt', mode='rt', encoding='utf-8') as f:
for line in f:
username, password = line.strip().split(':')
if inp_username == username and inp_password == password:
print('login successfull')
break
else:
print('账号或密码错误')
w操作模式:只写模式,当文件不存在时会创建空文件,当文件存在会清空文件,指针位于开始位置.
with open('d.txt', mode='wt', encoding='utf-8') as f:
# f.read() # 报错,不可读
f.write('我擦勒\n')
强调两个点:
-
在以w模式打开文件没有关闭的情况下,连续写入,新的内容总是跟在旧的之后。
with open('d.txt', mode='wt', encoding='utf-8') as f: f.write('擦勒1\n') f.write('擦勒2\n') f.write('擦勒3\n')
-
如果重新以w模式打开文件,则会清空文件内容。
with open('d.txt', mode='wt', encoding='utf-8') as f: f.write('擦勒1\n') with open('d.txt', mode='wt', encoding='utf-8') as f: f.write('擦勒2\n') with open('d.txt', mode='wt', encoding='utf-8') as f: f.write('擦勒3\n')
# == == == == == == == =案例 == == == == == == == == ==
# 案例:w模式用来创建全新的文件
# 文件文件的copy工具
src_file = input('被复制文件路径>>: ').strip()
dst_file = input('复制文件的路径>>: ').strip()
with open(r'{}'.format(src_file), mode='rt', encoding='utf-8') as f1, \
open(r'{}'.format(dst_file), mode='wt', encoding='utf-8') as f2:
# res = f1.read()
# f2.write(res)
for line in f1:
f2.write(line)
# 这么写并不会提升程序运行速度,但相对会大大减少内存占用。
# res = f1.read()会在一瞬间加大内存占用量,特别是在文件数据量大的情况下。
a操作模式:只追加写模式,在文件不存在时会创建空文档,在文件存在时文件指针会直接调到末尾。
with open('e.txt', mode='at', encoding='utf-8') as f:
# f.read() # 报错,不能读
f.write('我擦嘞\n')
强调 w 模式与 a 模式的异同:
- 相同点:在打开的文件不关闭的情况下,连续的写入,新写的内容总会跟在前写的内容之后。
- 不同点:以 a 模式重新打开文件,不会清空原文件内容,会将文件指针直接移动到文件末尾,新写的内容永远写在最后。
a模式用来在原有的文件内存的基础之上写入新的内容,比如记录日志、注册信息等。
# == == == == == == == =案例 == == == == == == == == ==
# 注册功能
name = input('your name>>: ')
pwd = input('your name>>: ')
with open('db.txt', mode='at', encoding='utf-8') as f:
f.write('{}:{}\n'.format(name, pwd))
“+”操作模式(了解):"+"不能单独使用,必须配合r、w、a
r+ w+ a+ :可读可写
在平时工作中,我们只单纯使用r/w/a,要么只读,要么只写,一般不用可读可写的模式。
with open('g.txt', mode='rt+', encoding='utf-8') as f:
f.write('中国')
# print(f.read()) # 读不出来,因为写了之后光标在末尾。
with open('g.txt', mode='w+t', encoding='utf-8') as f:
print(f.read()) # 读不出来,w模式已经把内容清空了
f.write('111\n')
f.write('222\n')
f.write('333\n')
print('====>', f.read()) # 读不出来,因为光标在末尾。
with open('g.txt', mode='a+t', encoding='utf-8') as f:
print(f.read()) # 读不出来,因为a模式下光标在末尾,后面没有内容。
f.write('444\n')
f.write('5555\n') # 写入后光标停在了文档末尾的地方
print(f.read()) # 读不出来,因为光标指针后面没有内容。
x操作模式(了解):只写模式【不可读;不存在则创建,存在则报错】
with open('a.txt', mode='x', encoding='utf-8') as f:
pass # 默认t文件模式,所以实质是xt
with open('c.txt', mode='x', encoding='utf-8') as f:
f.read() # 报错,不可读
with open('d.txt', mode='x', encoding='utf-8') as f:
f.write('哈哈哈\n')
b文件模式:binary模式
- 读写都是以bytes为单位
- 可以针对所有文件
- 一定不能指定字符编码,即一定不能指定encoding参数
# d.txt内容为:哈哈哈a
with open(r'd.txt', mode='rb') as f:
res = f.read() # utf-8的二进制
print(res, type(res)) # b'\xe5\x93\x88\xe5\x93\x88\xe5\x93\x88a' <class 'bytes'>
# 为什么末尾的a没有以二进制形式输出?因为解释器自动对英文的二进制输出做了转换。
print(res.decode('utf-8')) # 哈哈哈a # 这里bytes解码后为str
with open(r'f.txt', mode='wb') as f:
f.write('你好hello'.encode('utf-8'))
# 重写文件拷贝工具,可以拷贝任意文件
src_file=input('源文件路径>>: ').strip()
dst_file=input('源文件路径>>: ').strip()
with open(r'{}'.format(src_file),mode='rb') as f1,\
open(r'{}'.format(dst_file),mode='wb') as f2:
for line in f1:
f2.write(line)
得到bytes类型的三种方式
- 字符串编码之后的结果
- ‘字’.encode(‘utf-8’)
- bytes(‘字’, encoding=‘utf-8’)
- b’必须是纯英文字符’
- b模式下打开文件,f.read()读出的内容
循环读取文件的两种方式
# 方式一:自己控制每次读取的数据的数据量
with open(r'test.jpg', mode='rb') as f:
while True:
res = f.read(1024) # 1024个字节
if len(res) == 0:
break
print(len(res))
# 方式二:以行为单位读,当一行内容过长时会导致一次性读入内容的数据量过大
with open(r'g.txt', mode='rt', encoding='utf-8') as f:
for line in f:
print(len(line), line)
with open(r'g.txt', mode='rb') as f:
for line in f:
print(line)
with open(r'test.jpg', mode='rb') as f:
for line in f:
print(line)
总结:
- 在操作纯文本文件方面 t模式 帮我们省去了编码与解码的环节,b模式 则需要手动编码与解码(读写都需要),所以此时 t模式 更为方便。
- 针对非文本文件(如图片、视频、音频等)只能使用 b模式。
五、文件操作的其他方法
读相关操作
-
readline:一次读一行
g.txt文件内容:
111 222 333 444 5555
with open(r'g.txt', mode='rt', encoding='utf-8') as f: res1 = f.readline() res2 = f.readline() print(res1) # 111 while True: line = f.readline() if len(line) == 0: break print(line.strip()) # 333 # 444 # 5555
-
readlines:一次读所有行
with open(r'g.txt', mode='rt', encoding='utf-8') as f: res = f.readlines() print(res) # ['111\n', '222\n', '333\n', '444\n', '5555']
**强调:**f.read()与f.readlines()都是将内容一次性读入内存。
写相关操作
-
f.writelines():
有一个列表l,将列表的内容写入h.txt中。
l = ['11111\n', '2222', '3333'] # l = ['11111\n', '2222', '3333', 4444] 若列表中有非字符串类型元素,f.write()就会报错。 with open('h.txt', mode='wt', encoding='utf-8') as f: for line in l: f.write(line) # h.txt内容为: # 11111 # 22223333
f.writelines()的作用,就是上述代码块中for循环代码所做的事情。
l = ['11111\n', '2222', '3333'] with open('h.txt', mode='wt', encoding='utf-8') as f: f.writelines(l)
b模式写入需要编码:
with open('h.txt', mode='wb') as f: l = [ '1111aaa1\n'.encode('utf-8'), '222bb2'.encode('utf-8'), '33eee33'.encode('utf-8') ] f.writelines(l) # h.txt内容为: # 1111aaa1 # 222bb233eee33
补充两点:
-
补充1:如果是纯英文字符,可以直接加前缀b得到bytes类型。
with open('h.txt', mode='wb') as f: l = [ b'1111aaa1\n', b'222bb2', b'33eee33' ] f.writelines(l) # 写法虽然不一样但结果和上一个一样
-
补充2:‘文字’.encode(‘utf-8’) 等同于bytes(‘文字’ , encoding=‘utf-8’)
with open('h.txt', mode='wb') as f: l = [ bytes('上啊', encoding='utf-8'), bytes('冲呀', encoding='utf-8'), bytes('小垃圾们', encoding='utf-8'), ] f.writelines(l) # h.txt内容为: # 上啊冲呀小垃圾们 # 所以将文字转换为bytes类型就有了两种方法。 # '文字'.encode('utf-8') # bytes('文字' , encoding='utf-8')
-
了解的几个:
- f.flush():立刻将文件内容从内存刷到硬盘
- f.readable():判断文件是否可读
- f.writable():判断文件是否可写
- f.closed():判断文件是否已经关闭
- f.encoding():文件的字符编码是什么(如果文件打开模式为b,则没有该属性)
- f.name():文件的名字是什么
六、文件的高级操作:控制文件指针的移动
指针移动的单位都是以bytes(字节)为单位,只有一种情况特殊,就是t模式下的read(n),n代表的是字符个数。
# aaa.txt内容为:abc你好
# 共五个字符9个字节
with open('aaa.txt', mode='rt', encoding='utf-8') as f:
res = f.read(4)
print(res) # abc你
f.tell():获取文件指针当前位置
f.seek(n , 模式):n指的是移动的字节个数
三种模式:
强调:只有0模式可以在t模式下使用,1、2 必须在b模式下用,否则会报错。
with open('aaa.txt', mode='rb') as f:
f.seek(9, 0)
f.seek(3, 0) # 3
print(f.tell()) # 结果为3
# aaa.txt内容为:abc你好
# 9个字节(一个英文占一个字节,一个汉字占三个字节)
with open('aaa.txt', mode='rb') as f:
f.seek(9, 0)
f.seek(3, 0) # 3
res = f.read()
print(res.decode('utf-8')) # 你好
# f.seek(4, 0) # 移动到第四个字节末尾的位置
# res2 = f.read()
# print(res.decode('utf-8')) # 报错,因为识别不完整。
with open('aaa.txt', mode='rb') as f:
f.seek(9, 1)
f.seek(3, 1) # 12
print(f.tell()) # 结果为12
# aaa.txt内容为:abc你好
with open('aaa.txt', mode='rb') as f:
f.seek(-9, 2)
# print(f.tell()) # 结果为:0
f.seek(-3, 2)
# print(f.tell()) # 结果为:6
print(f.read().decode('utf-8')) # 结果为:好
小案例:实现日志监控功能
两个程序,一个写日志,一个监控并输出。
写日志:
import time
n = 0
with open('access.log', mode='at', encoding='utf-8') as f:
while True:
n += 1
f.write('第{}次日志\n'.format(n))
time.sleep(2) # 延时2秒
if n == 10:
break
监控输出:
import time
with open('access.log', mode='rb') as f:
f.seek(0, 2)
while True:
line = f.readline()
if len(line) == 0:
time.sleep(0.3)
else:
print(line.decode('utf-8'), end='')
七、文件修改的两种方式
# 文件a.txt内容如下
张一蛋 山东 179 49 12344234523
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
# 执行操作
with open('a.txt',mode='r+t',encoding='utf-8') as f:
f.seek(9)
f.write('<妇女主任>')
# 文件修改后的内容如下
张一蛋<妇女主任> 179 49 12344234523 # 发生了覆盖,并不是在中间插入
李二蛋 河北 163 57 13913453521
王全蛋 山西 153 62 18651433422
# 强调:
# 1、硬盘空间是无法修改的,硬盘中数据的更新都是用新内容覆盖旧内容
# 2、内存中的数据是可以修改的
文件对应的是硬盘空间,硬盘不能修改对应着文件本质也不能修改,那我们看到文件的内容可以修改,是如何实现的呢?
大致的思路是将硬盘中文件内容读入内存,然后在内存中修改完毕后再覆盖回硬盘,具体的实现方式分为两种:
c.txt内容:
张三 is sb
李四 is dsb
王五 is xsb
方式一(文本编辑采用的就是这种方式)
实现思路:将文件内容发一次性全部读入内存, 然后在内存中修改完毕后再覆盖写回原文件。
- 优点: 在文件修改过程中同一份数据只有一份
- 缺点: 会过多地占用内存
with open('c.txt', mode='rt', encoding='utf-8') as f:
res = f.read()
data = res.replace('sb', '**')
print(data)
with open('c.txt', mode='wt', encoding='utf-8') as f1:
f1.write(data)
# 运行后,c.txt内容如下
# 张三 is **
# 李四 is d**
# 王五 is x**
方式二
实现思路:以读的方式打开原文件,以写的方式打开一个临时文件,一行行读取原文件内容,修改完后写入临时文件…,删掉原文件,将临时文件重命名原文件名。
- 优点: 不会占用过多的内存
- 缺点: 在文件修改过程中同一份数据存了两份
import os
with open('c.txt', mode='rt', encoding='utf-8') as f, \
open('.c.txt.swap', mode='wt', encoding='utf-8') as f1:
for line in f:
f1.write(line.replace('sb', '***'))
os.remove('c.txt') # 删除c.txt文件
os.rename('.c.txt.swap', 'c.txt') # 将新文件文件名改成c.txt
# 注意:.c.txt.swap文件在硬盘里,不在内存里。
# 运行后,c.txt内容如下
# 张三 is ***
# 李四 is d***
# 王五 is x***