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', '大奖获得丰厚都十分']
rt
# 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')
wt

 

实现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:光标移动到文件末尾

posted @ 2019-05-21 19:17  呔!妖精。。。  阅读(195)  评论(0编辑  收藏  举报