欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python文件操作

程序员文章站 2022-04-13 22:51:41
python文件操作 三元运算 字符编码转换 文件处理 三元运算 三元运算又称为三目运算, 是对简单的条件语句的简写 。 text 姓名 年龄 联系电话 老王 23 13003123113 小李 20 18947123319 张三 25 13888889888 李四 18 18492134132 p ......

python文件操作

  • 三元运算
  • 字符编码转换
  • 文件处理

三元运算

三元运算又称为三目运算,是对简单的条件语句的简写

@requires_authorization
# 简单条件语句
a = 2
b = 5
if a < b:  # if判断大小
    val = a
else:
    val = b
print(val)  # val = 2

# 改写成三元运算
val = a if a < b else b
print('val:',val)  # val: 2

字符编码转换

编码回顾

python编码为什么这么蛋疼?

这哥们的这段话说的太对了,搞Python不把编码彻底搞明白,总有一天它会猝不及防坑你一把。

回顾一下编码种类情况

ASCII       占1个字节       只支持英文
GB2312      占2个字节       支持6700+汉字
GBK         GB2312的升级版  支持21000+汉字
Shift_JIS           日本编码
ks_c_5601_1987      韩国编码
TIS-620             泰国编码

由于每个国家都有自己的字符,所以其对应关系也涵盖了自己国家的字符,但是以上编码都存在局限性,即:仅局限本国字符,无其他国家字符的对应关系。应运而生了万国码,它涵盖了全球所有的文字和二进制对应关系

  • Unicode 2-4 个字节,已经收录了136690个字符,并还在不断扩张中

Unicode起到了2个作用:

  • 直接支持全球所有语言,每个国家都可以不再使用自己之前的旧编码,用Unicode就可以了
  • Unicode包含了跟全球所有国家编码的映射关系

Unicode解决了字符和二进制的对应关系,但是使用Unicode表示一个字符,太浪费空间,比如:利用Unicode表示"python"需要12个字节才能解决,比原来ASCII表示增加了1倍。

由于计算机的内存比较大,并且字符串再内容中表示时也不会特别大,所以内容用Unicode来处理,但是在存储和网络传输中一般数据会比较多,那么增加1倍是无法容忍的!

为了解决存储和网络传输的问题,出现了UTF,即:对Unicode中的进行转换,以便于在存储和网络传输时可以节省空间。

  • UTF-8:使用1-4个字节来表示所有字符;优先使用1个字符,无法满足则使增加1个字节,最多4个字节。英文占1个字节 ,欧洲语系占2个、东亚占3个,其它及特殊字符占4个。
  • UTF-16:使用2-4个字节表示所有字符;优先使用2个字节,否则使用4个字节表示。
  • UTF-32:使用4个字节来表示所有字符。

总结:UTF是为了Unicode编码设计的一种在存储和传输时节省空间的编码方案

文件处理

引子

问题1:给你一个文件 "学生名单.txt" ,如何查看内容?

答:

1.安装文本编辑器软件

2.选中右键,利用文本编辑器软件打开

3.查看 or 写入

4.保存,关闭

问题2:文件在硬盘上是如何存储的?

答:

以某种编码格式的"01010101010101"保存在硬盘

python处理文件

python文件分为读、写、修改

读文件

首先在pycharm定义 "联系方式.txt" 文本文件

姓名  年龄  联系电话
老王  23  13003123113
小李  20  18947123319
张三  25  13888889888
李四  18  18492134132

那么我们想读取到文件中的内容,需要怎么办呢?

示例1:

f = open('联系方式.txt',mode='r',encoding='UTF-8')  
data = f.read()  
print(data)
f.close()  

'联系方式.txt' 表示路径,因为就在当前目录下,所以不用写路径也可以
mode = 'r' 表示只读模式
encoding = 'UTF-8' 表示将硬盘上的01010101按照UTF-8的规则去断句,再将“断句”后的每一段0101010转换成unicode的 01010101,unicode对照表中有01010101和字符的对应关系。
f.read() 表示读取所有内容,内容是已经转换完毕的字符串
f.close() 表示关闭文件

# 在这里需要注意的是,用什么编码存的文件,就要用什么编码去打开
# 还有一种打开的方式
f = open(file = 'D:\py_study\day06-文件处理\联系方式.txt',mode = 'r',encoding='utf-8')
data = f.read()
print(data)
f.close()

解释一下区别:
    如果要打开的这个文件存放在当前目录,那么就可以不用写绝对路径,只需要写相对路径就可以(或者可以不用写)
    如果打开的文件不存放在当前目录,那么就必须要写上绝对路径

示例2:

f = open('联系方式.txt','rb')
data = f.read()
print(data)  # 打印的是16进制
f.close()

'联系方式.txt'  # 代表的是文件路径,因为在当前目录下,所以就不用写路径了
mode = 'rb'  # 表示只读
f.read()  # 表示读取所有内容,内容是硬盘上原来以某种编码保存的 010101010,即:某种编码格式的字节类型
f.close()  # 表示关闭文件 

运行结果如下:

姓名  年龄  联系电话
老王  23  13003123113
小李  20  18947123319
张三  25  13888889888
李四  18  18492134132

那么示例1和示例2有什么区别呢?

在于示例2并没有指定encoding,这是为何?是因为直接以rb的模式打开了文件,rb是指二进制模式,数据读到内存李直接是bytes格式,如果想内容,还需要手动decode,因此在文件打开阶段,不需要指定编码。

那么,我们不知道要处理的文件编码怎么办呢?

import chardet  # 导入工具包
f = open('联系方式.txt',mode='rb')  # 以二进制模式打开
data = f.read()
f.close()

result = chardet.detect(open('联系方式.txt',mode = 'rb').read())  # 将读取的文件交给detect函数去处理
print(result)

运行结果为:

{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}  # 即99%几率是utf-8

注意:

  • 文件操作时,以r或者rb模式打开,则只能读,无法写入;
  • 硬盘上保存的文件都是某种编码的0101010,打开时需要注意:
  • rb,直接读取文件保存时原生的01010101,在python中用字节类型表示
  • r和encoding,读取硬盘上的01001010,并按照encoding指定的编码格式进行断句,再将断句后的每一段0101001转换成unicode的01010101101,在python中用字符串类型表示

循环文件

f = open('联系方式.txt','r',encoding='utf-8')
for line in f:
    print(line)  # 会有空格
f.close()

运行结果如下:

小明  北京  10402384103

老王  上海  10403145103

校长  深圳  13888888888

王寒  珠海  12389874589

黎明  北京  10123451313

写文件

f = open('写文件.txt','w',encoding='utf-8')
f.write('北大本科美国留学一次50,微信号:xxxxx')
f.close()

上述操作语法解释:

'写文件.txt'  # 表示文件路径
mode = 'w'  # 表示只写
encoding='utf-8'  # 将要写入的unicode字符编码成utf-8编码
f.write()  # 将写入文件内容,写入的内容是unicode字符串类型,内部会根据encoding转换为制定编码的 01101010101,即:字节类型
f.close()  # 关闭文件

二进制写

# 二进制写
f = open('写文件.txt',mode='wb')
f.write(' hello python'.encode('utf-8'))
f.close()

注意:

1、文件操作时,以w或者wb模式打开,则只能写,并且在打开的同时会先将内容清空。

2、写入到硬盘上时,必须是某种编码的0101010,打开时需要注意

  • wb,写入时需要直接传入以某种编码的01010100,即:字节类型
  • w和encoding,写入时需要传入unicode字符串,内部会根据encoding指定的编码将unicode字符串转换为该编码的01010101

追加

将内容增加到文件尾部

f = open('联系方式.txt','a',encoding='utf-8')
f.write('\n杜珊珊  北京  13004562134')
f.close()

运行结果:

小明  北京  10402384103
老王  上海  10403145103
校长  深圳  13888888888
王寒  珠海  12389874589
黎明  北京  10123451313
杜珊珊  北京  13004562134  # 这行是新添加的

注意:

1、文件操作时,以a或ab模式打开,则只能追加,即:在原有的内容的尾部追加内容
2、写入到硬盘上时,必须是某种编码的01010101,打开时需要注意:

  • ab,写入时要直接传入以某种编码的01010101,即:字节类型
  • a和encoding,写入时需要传入unicode字符串,内部会根据encoding制定的编码将unicode字符串转换为该编码的 010101010

读写模式

读写模式

读写模式,将内容追加到文件尾部

f = open('联系方式.txt','r+',encoding='utf-8')
data = f.read()
print(data)
f.write('\n 肖亚飞 河南  12345678123')
f.close()

运行结果如下:

 肖亚飞 河南  12345678123
 肖亚飞 河南  12345678123
 肖亚飞 河南  12345678123
 肖亚飞 河南  12345678123
 肖亚飞 河南  12345678123
 肖亚飞 河南  12345678123

写读模式

写读模式,会将源文件中内容全部清空掉

f = open('写文件.txt',mode='w+',encoding='utf-8')
f.write(' 肖亚飞 河南  12345678123')
f.close()

运行结果如下:

 肖亚飞 河南  12345678123

文件操作的其他功能

def fileno(self, *args, **kwargs): # real signature unknown
        返回文件句柄在内核中的索引值,以后做IO多路复用时可以用到

    def flush(self, *args, **kwargs): # real signature unknown
        把文件从内存buffer里强制刷新到硬盘

    def readable(self, *args, **kwargs): # real signature unknown
        判断是否可读

    def readline(self, *args, **kwargs): # real signature unknown
        只读一行,遇到\r or \n为止

    def seek(self, *args, **kwargs): # real signature unknown
        把操作文件的光标移到指定位置
        *注意seek的长度是按字节算的, 字符编码存每个字符所占的字节长度不一样。
        如“肖亚飞” 用gbk存是2个字节一个字,用utf-8就是3个字节,因此以gbk打开时,seek(4) 就把光标切换到了“亚”和“飞”两个字中间。
        但如果是utf8,seek(4)会导致,拿到了亚这个字的一部分字节,打印的话会报错,因为处理剩下的文本时发现用utf8处理不了了,因为编码对不上了。少了一个字节

    def seekable(self, *args, **kwargs): # real signature unknown
        判断文件是否可进行seek操作

    def tell(self, *args, **kwargs): # real signature unknown
        返回当前文件操作光标位置 

    def truncate(self, *args, **kwargs): # real signature unknown
        按指定长度截断文件
        *指定长度的话,就从文件开头开始截断指定长度,不指定长度的话,就从当前位置到文件尾部的内容全去掉。

    def writable(self, *args, **kwargs): # real signature unknown
        判断文件是否可写

flush()把文件从内存buffer里强制刷新到硬盘

T>>> f = open('flush.txt','w',encoding='utf-8')
>>> f.write('xiaoyafei')  # 因为此时写完后,文件还存放在内存里,并没有写进硬盘里
9
>>> f.flush()  # 把文件从内存里强制刷新到硬盘
>>> f.close()

readable()判断是否可读,可以则返回True

f = open('1.txt','r',encoding='utf-8')
print(f.readable())  # True
f.close()  

readline()只读一行,遇到\r or \n为止

 # 联系方式.txt

小明-中国-北京    1
老王-日本-东京    1

代码:

f = open('联系方式.txt','r',encoding='utf-8')
data = f.readline()
print(data)  # 一直会打印  小明-中国-北京    1 
f.close()

seek()把操作文件的光标移到指定位置
tell()返回光标所在位置

# admin.txt  utf-8

admin-admin-admin-1
admin-admin-admin-2
admin-admin-admin-3
admin-admin-admin-4

代码:

f = open('admin.txt','r',encoding='utf-8')
print(f.tell())  # 0
print(f.readline())  # admin-admin-admin-1

print(f.tell())  # 21

f.seek(2)  # 光标为2
print(f.readline())  # min-admin-admin-1

注意

1.f.read()找的是字符

2.f.tell()和f.seek()找的是字节

seekable() 判断文件是否可以进行seek操作

# 刚刚的admin.txt 文件不修改

f = open('admin.txt','r',encoding='utf-8')
print(f.seekable())  # True
f.close()  

writeable() 判断文件是否能写操作

f = open('admin.txt','r',encoding='utf-8')  # mode = 'r' 表示只读
print(f.writable())  # False
f.close()

f = open('admin.txt','w',encoding='utf-8')  # mode = 'w' 表示写
print(f.writable())  # True
f.close()

truncate() 按指定长度截断文件,从当前位置往后截断

# f_flush.txt文件内容:

肖亚飞

代码:

f = open('f_flush.txt','r+',encoding='utf-8')
res = f.read()
print(res)  # 肖亚飞
print(f.truncate(6))  # 6
f.flush()  # 肖亚
f.close()

占硬盘方式的文件修改代码示例:

# 联系方式.txt文件内容

小明     深圳    173    50    13744234523
康康     广州    172    52    15823423525
玛利     北京    175    49    18623423421
刘诺     北京    170    48    18623423765
老张     广州    172    52    15823423514
罗梦     北京    175    49    18623423311
刘诺     北京    170    48    18623423315

代码:

import os  # 导入工具包

f_name = '联系方式.txt'  # 旧的文件名
f_new_name = '%s.new' % f_name  # 新的文件名

old_str = '阿富汗'  # 将要替换的字符串
new_str = '中国'  # 替换成...

f = open(f_name,mode='r',encoding='utf-8')  # 打开文件进行读操作
f_new = open(f_new_name,mode='w',encoding='utf-8')  # 打开文件进行写操作

for line in f:  # 循环文件
    if old_str in line:  # 如果old_str在line中
        line = line.replace(old_str,new_str)  # 替换old_str为new_str

    f_new.write(line)  # 写入文件内容

f.close()
f_new.close()

补充1:

我们已经学习了seek,那么现在告诉你,之所以文件会追加到追后面,是因为,文件一打开,要写的时候,光标会默认移动到文件尾部,再开始写,那么,我现在想修改中间部分怎么办?

小明     深圳    173    50    13744234523
康康     广州    172    52    15823423525
玛利     北京    175    49    18623423421
刘诺     北京    170    48    18623423765
老张     广州    172    52    15823423514
罗梦     北京    175    49    18623423311
刘诺     北京    170    48    18623423315

运行完一下代码会是什么样的?:

f = open('联系方式.txt','r+',encoding='utf-8')
f.seek(6)
f.write('xiaoyafeibaba')
f.close()

运行结果:

小明xiaoyafeibaba  173    50    13744234523
康康     广州    172    52    15823423525
玛利     北京    175    49    18623423421
刘诺     北京    170    48    18623423765
老张     广州    172    52    15823423514
罗梦     北京    175    49    18623423311
刘诺     北京    170    48    18623423315

f.write()确实是从第6个字节开始改的,但是把后面文件的内容覆盖了,这是怎么回事?

这是硬盘的存储原理导致的,当你把文件存到硬盘上,就在硬盘上划了一块空间,存数据,等你下次打开这个文件 ,seek到一个位置,每改一个字,就是把原来的覆盖掉,如果要插入,是不可能的,因为后面的数据在硬盘上不会整体向后移。所以就出现 当前这个情况 ,你想插入,却变成了会把旧内容覆盖掉。

但为什么word和vim可以修改文件呢?

我并没说就不能修改了,你想修改当然可以,就是不要在硬盘上修改,把内容全部读到内存里,数据在内存里可以随便增删改查,修改之后,把内容再全部写回硬盘,把原来的数据全部覆盖掉。vim word等各种文本编辑器都是这么干的。

说的好像有道理,但你又没看过word软件的源码,你凭什么这么笃定?

哈哈,我不需要看源码,硬盘 的存储原理决定了word必须这么干 ,不信的话,还有个简单的办法来确认我说的,就是用word or vim读一个编辑一个大文件 ,至少几百MB的,你 会发现,加载过程会花个数十秒,这段时间干嘛了? cpu 去玩了?去上厕所啦? 当然不是,是在努力把数据 从硬盘上读到内存里。

但是文件如果特别大,比如5个GB,读到内存,就一下子吃掉了5GB内存,好费资源呀,有没有更好的办法呢?

如果不想占内存,只能用另外一种办法啦,就是边读边改, 什么意思? 不是不能改么?是不能改原文件 ,但你可以打开旧文件 的同时,生成一个新文件呀,边从旧的里面一行行的读,边往新的一行行写,遇到需要修改就改了再写到新文件 ,这样,在内存里一直只存一行内容。就不占内存了。 但这样也有一个缺点,就是虽然不占内存 ,但是占硬盘,每次修改,都要生成一份新文件,虽然改完后,可以把旧的覆盖掉,但在改的过程中,还是有2份数据 的。