python进阶之 ——文件处理
文件是操作系统中的一个虚拟概念,是以计算机硬盘为载体存储在计算机上的信息集合,文件可以是文本文档、图片、程序等等。在系统运行时,计算机以进程为基本单位进行资源的调度和分配;而在用户进行的输入、输出中,则以文件为基本单位。大多数应用程序的输入都是通过文件来实现的。
在初期编写程序时,接触最多的是文本文件,比如,在注册和登录功能中,用户名和密码要存储在文件里,python程序也是写成文本文件的形式。
打开一个文本文档的过程:
• 启动文本编辑器(程序加载到内存),开始编辑(此时即时输入的内容是存在了内存)
• 输入→显示(过程中,涉及到中文字符→二进制→中文字符的过程)
• 写完保存,编辑器把内存中的数据保存到硬盘
字符→二进制→字符的过程,就是编码(encode)(输入),解码(decode)(输出)的过程
执行一个python文件的过程:
• Cpython解释器加载到内存
• py文件从硬盘加载到内存
• 解释器向CPU发出指令,处理py文件,逐行识别文本的语法
一、字符编码
字符编码就是字符按照某种标准转换(编码)成数字(二进制位)的过程,这个标准叫字符编码表。
最开始的计算机,只在英文环境里使用,所以编码解码只考虑英文字符和二进制,具体用哪些二进制数字表示哪个符号,经最早的这批人达成一致,这就是ASCII码。 后来计算机普及,每个国家都产生了自己的语言与二进制的对应标准,(如中国的GBK)因为各个国家不统一,导致了这样的问题:汉语国家编写的软件,在其他语系国家里不能使用,因为他们的计算机中没有汉语字符与二进制的对照关系!为了解决这样的问题,unicode码诞生了,它将很多国家的字符与二进制的关系都包含在内,统一使用16位二进制数字表示各国字符。 结果就是,我们在自己的电脑文本编辑器里输入一个日文字符,编码到内存,显示(输出)的时候,这16位二进制数字会解码成日文,显示出来,而不会出现乱码的问题。 可是再后来,使用计算机的过程中,又发现了新问题:在英语系国家的编程者发现,原来采用ASCII码,用8位二进制表示字符,而现在需要16位,输入的数据存入硬盘的时间几乎长了一倍!为了解决这样的问题,又出现了UTF-8(8-bit Unicode Transformation Format),这是一种针对Unicode的可变长度字符编码,它会识别unicode编码的二进制表示的字符是什么语言的字符,并分配不同的字节,比如,如果是英文,就用1个字节存储到硬盘,如果是中文,就用3个字节存到硬盘 有了UTF-8之后,只要我们写一个文件,保存成utf-8编码格式,在任何国家的电脑上都能通用,而且占的空间更小,存取更快! 但是utf-8不可以完全取代unicode,因为在这之前,很多国家都用自己国家的编码标准编写了软件,而utf-8里只有与unicode 一 一对应的关系,并没有直接和GBK,shift-jis等标准建立联系,所以直至现在,我们的计算机内存采用的,仍然是unicode。 就像python3刚推出时,很多python2的软件都不能用python3运行,导致很多公司和个人拒绝使用python3,于是龟叔迫不得已推出python2.7一样,我们需要unicode来作为阶段性的过渡。 但是如果以后每个人写文件,都采用utf-8格式保存,等足够长的时间后,就可以废弃unicode标准了,也就不会存在因为编码解码而导致的程序报错和乱码问题!!
解决乱码问题的关键,就是以什么格式编码的,就用什么格式解码,要保证编码解码一致,最好清晰的、人为的指定编码解码方式。比如在文件最上方添加文件头:#coding:utf-8(文件当初存的字符编码)。
python解释器默认的字符编码: python2:默认ASCII python3:默认utf-8 字符的指定编码转换方法: x='张' res=x.encode('gbk') m=res.decode('gbk')
字符--------编码--------》数字 字符《--------解码--------数字 unicode utf-8二进制 1. ASCII表:只能识别英文字符,用8bit对应一个英文字符 2. GBK表:可以识别中文字符、英文字符,用8bit对应一个英文字符,用16个bit对应一个中文字符 3. unicode(内存中默认使用该编码):用2Bytes表示一个字符 4. utf-8全称Unicode Transformation Format:用8bit对应一个英文字符,用24个bit对应一个中文字符 内存中固定使用unicode编码,我们可以改变的是数据由内存刷到硬盘时采用的编码(应该采用utf-8) unicode不是字符集,他是一个标准,utf-8是这个标准的一种实现方式。 unicode的特点: 1. 可以识别万国字符 2. 与各种字符编码的二进制数字都有对应关系 python3的字符串类型在内存中存成unicode格式的二进制
二、文件处理
文件是操作系统为了方便用户和应用程序管理磁盘空间,提供的虚拟单位。文件对应着磁盘空间,为了永久在磁盘存储数据,就要使用文件。
应用程序和用户对文件进行读写操作都是向操作系统发送请求,由操作系统对外发送指令。
对文件进行操作首先要找到文件,也就是知道文件路径,路径有两种:绝对路径和相对路径。
绝对路径:文件所在文件夹在系统中的完整位置 windows系统:D:\A_my_practice linux系统:/use/local/lib 相对路径:是相对于执行文件所在文件夹的位置。
找到文件后,就可以对文件进行打开、读写、关闭操作。
打开 文件路径,指定读写模式(只读,以text的格式),指定当时保存的编码格式(也就是指定解码格式) file1 = open(r'D:\A_my_practice\a.txt', mode='rt', encoding='utf-8') res = file1.read() # 读文件 print(res) # 输出读取结果 file1.close() # 关闭文件
这样打开文件,操作结束后一定要关闭,这里涉及执行文件时的内存管理:
打开一个文件时,file1首先是python程序内存里的一个变量,占用python的内存空间。而file1的值指向一个文件,文件是操作系统这个程序产生的,所以也占用操作系统的内存。
python解释器有自己的垃圾回收机制,而操作系统没有,为了不浪费内存,需要我们主动关闭文件。
还有一种打开方法,不用主动关闭:
上下文管理: with open('a.txt',mode='wt',encoding='utf-8') as f: f.write("123\n")
以上面的代码为例,总结文件的基本操作:
打开文件有两种方式如上,open() 括号内都要指定路径,r‘路径’ 表示路径字符串是原生字符串,里面的\ 没有转义的含义。mode指定文件的操作模式:
r | 只读,从文件头读到文件末尾,文件不存在时报错 | b |
无论读写都是以bytes(二进制)为单位的, 可以操作所有文件,一定不能指定encoding参数 |
w |
只写,在打开时将文件内容清空,写入指定内容 文件不存时创建空文件 |
t(默认) |
无论读写都是以字符为单位的, 只能操作文本文件,必须指定encoding参数 |
a |
追加写,打开时指针移到行末,追加写入的内容 文件不存时创建空文件 |
+ |
r+ 可读可写 w+ 可写可读 a+ 可追加写可读 |
with open('a.txt',mode='rt',encoding='utf-8') as f: # data=f.read() # 读整体 # print(f.readable()) # True 只读 # print(f.writable()) # False # line=f.readline() # 一次读一行 # print(line,end='') # lalal # print(data,type(data)) #大奖获得丰厚都十分 <class 'str'> # for line in f: # print(line) #lalal 大奖获得丰厚都十分 # print(f.readlines()) # 把内容全读出,返回一个列表 pass #['lalal\n', '大奖获得丰厚都十分']
# with open('a.txt',mode='wt',encoding='utf-8') as f: # print (f.writeines()) # with open('a.txt',mode='wt',encoding='utf-8') as f: # print(f.readable()) # False # print(f.writable()) # True 只写 # 在打开了文件不关闭的情况下,连续的写入,新写的内容总是跟在老内容之后 # f.write('你瞅啥\n') # f.write('瞅你咋的\n') # f.write('巴拉巴拉。。。\n') # # lines=['1111\n','2222\n','3333\n'] # for line in lines: # 全部写入 # f.write(line) # f.writelines(lines) # 一次写一行 # f.write('aaaa\nbbb\ncccc\n')
实现copy 功能(一次性读取文件全部内容,在内存中完成复制后存入磁盘):
with open('a.txt','rb') as file1,open('b.txt','wb') as file2: file2.write(file1.read()) # 如果文件过大,会占用过多内存 for line in file1: file2.write(line) # 这种方法逐行读取file1的内容,逐行写入file2。执行时只有一行在内存中,占用内容少。
三、文件修改的两种方式:
方式一:
1. 以读的方式打开源文件
2. 将文件内容一次性全读入内存,在内存完成修改
3. 以写的方式打开源文件,然后将修改后的结果一次性写入源文件
实现修改文件功能:
with open('a.txt','rt',encoding='utf-8') as f1: msg = f1.read() new_msg = msg.replace("world","python") with open('a.txt','wt',encoding='utf-8') as f2: f2.write(new_msg)
优点:节省磁盘空间,执行过程,只有一份数据在磁盘。编辑器采用此种方式。
缺点:如果文件过大,会占用过多内存,浪费内存
方式二
1. 以读的方式打开源文件,以写的方式打开一个临时文件
2. 读取源文件的一行内容到内存中,将修改的结果写入临时文件,循环往复直到改
3. 删除源文件,将临时文件重命名为源文件名
导入一个os模块:
import os with open('a.txt', 'rt', encoding='utf-8') as f1, \ open('.b.txt.swap', 'wt', encoding='utf-8') as f2: for line in f1: f2.write(line.replace("world", "python")) os.remove('a.txt') os.rename('.b.txt.swap', 'a.txt')
缺点:在文件修改过程中硬盘只存在两份数据,占用磁盘空间
优点:同一时间在内存中只有文件的一行内容,节省内存
在以上操作中,指针的移动都是靠读写命令来实现的,那么,怎样控制指针的移动呢?
在使用read()命令时,它能接收一个参数,用来控制读取的位置(指针的移动):
with open('a.txt', mode='rt', encoding='utf-8') as f: res = f.read(4) print(res) #在”t“模式下,f.read(4) 读取的是文本的前4个字符,除此以外,其他指针的操作都是按照字节为单位移动的 with open('a.txt', mode='rb') as f: res = f.read(4) print(res.decode('utf-8')) #如果是中文字符(一个字符占3个字节),这样读取前4个字节,第二个字符显示就会乱码。
truncate( )函数也可以控制指针,它会截取指定字节数的文本:
with open('a.txt', mode='at', encoding='utf-8') as f: f.truncate(3) # 截取前3个字节,之后的丢弃,这是一个写操作
其文件的打开方式必须可写,但是不能用w或w+等方式打开(直接清空文件),所以truncate要在r+或a或a+等模式下测试效果
前面的方法都带着读或写操作,而seek() 方法可以单纯的移动指针。它接收两个整形数字作为参数,第一个参数是移动的字节数,第二个整数指定参照位置,它有三种模式。
seek(self, offset: int, whence: int = 0)
with open('a.txt',mode='rt',encoding='utf-8') as f: f.seek(3,0) # 指针向右移动3个字节,0表示参照位置是文本文件开头,默认下seek() 的第二个参数是0 # 只有0模式既可以在t下用也可以在b下用,而1、2两种模式只能在b模式下使用 with open('a.txt',mode='rb') as f: f.read(2) f.seek(4,1) # 1 表示参照文件当前指针位置 print(f.tell()) # 打印当前指针位置 print(f.read().decode('utf-8')) with open('a.txt', mode='rb') as f: f.seek(-5, 2) # 2 参照文件末尾,负数向左,如果向右移动,读空格 print(f.tell()) print(f.read().decode('utf-8'))
用seek() 实现一个监测日志动态的小功能。日志存放于一个文本文档,此功能会定时检测有无程序向日志中写入内容,如果有,就会将刚写入的内容打印出来.
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.1) else: print(line.decode('utf-8'), end='')
f.tell:光标移动到文件末尾