一个完整的程序都是包括 数据的读取和存储,在实际开发中 经常性的需要从外存(硬盘、光盘和U盘等)里面读取数据,或者将程序执行过程中产生的中间数据存放在文件(日志等),以及将运算结果进行持久化保存等。这些过程都离不开与文件打交道!
根据文件中数据的组织形式,可以把文件分为文本文件和二进制文件:
在Python的标准库里面,有一些与文件操作相关的模块 如下是简要的描述:
模块名称 | 简要说明 |
---|---|
I/O模块 | 文件流的输入输出操作 |
OS模块 | 基于操作系统功能,包含文件操作 |
Glob模块 | 查找符合特定规则的文件路径名 |
Fnmatch模块 | 使用模式来匹配文件路径名 |
Filecmp模块 | 用于文件的比较 |
Cvs模块 | 使用于csv文件的处理 |
Pickle和cPickle模块 | 用于序列化和反序列化处理 |
xml包 | 用于xml数据处理 |
bz2、gzip、zipfile、zlib和tarfile | 用于处理压缩和解压缩文件(分别对应于不同的算法) |
在Python里面一切皆对象,open()创建出来的也是一个文件对象。其语法格式如下:
open(filename[,打开方式])
若是只是文件名,则代表当前目录下面的文件。(文件名可以使用绝对路径来指定)我们都知道在输入一个文件路径的时候,会使用到大量的 \ 。(这玩意儿在字符串里面整不好成了转义字符)因此为了减少 \的大量使用,我们使用原始字符串(就是路径的前面加上一个r)示例如下:
f=open(r"E:\Tsinghua_University\Python_Study\myexception3\venv\test.py","a")
文件的打开方式有下面几种:
文件模式 | 详细描述 |
---|---|
r | read |
w | write(文件不存在则创建;存在则重写新内容) |
a | append (文件不存在则创建;存在则追加写新内容) |
b | 二进制模式(可以与其他模式组合使用) |
+ | 读写模式(可以与其他模式组合使用) |
上面的文本文件和二进制文件对象的创建:若是没有使用 b模式,则默认为创建文本文件对象(处理的基本单元是 字符);否则为二进制文件对象(处理的基本单元是 字节)
文件文件的写入操作三部曲:
通常写入数据的时候有两个方法:write()和writelines()
# coding=utf-8
fp=open(r"test1.txt","a",encoding="utf-8")
str1="Tsinghua\nUniversity\n"
str2="清华\n大学\n"
fp.write(str1)
fp.write(str2)
fp.close()
#write()是把字符串写入到文件里面(若是多行,则需要换行符 \n)。它是不能够写入一个列表的,
#否则:TypeError: write() argument must be str, not list
而writelines()是支持写一个字符串列表到一个文件里面:
# coding=utf-8
fp=open(r"test1.txt","a",encoding="utf-8")
str1="Tsinghua\nUniversity\n"
strlist=["清华","大学\n","浙江","大学"]
fp.write(str1)
fp.writelines(strlist)
fp.close()
但是需要注意的是:writelines()函数在写入列表的时候,没有自动添加换行符(需要我们自己加)。
下面我们来看一个简单的例子:
# coding=utf-8
fp=open(r"test1.txt","a")
str1="Tsinghua\nUniversity\n"
str2="清华\n大学\n"
fp.write(str1)
fp.write(str2)
fp.close()
执行之后的结果如下:
我们的中文没有打印出来,显示却是乱码!
接下来我们稍微简单的介绍一下在计算机世界里面的几种字符集和编码:
ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
单字节即8位,且最高位现为0,也就是说使用了低7位(表示128个字符)。
这对于简单的English来说就足够用了,这点量跟博大精深的汉字 不能比 不能比!
如上图所示,对这个编码系统做个小结:
1、0到31表示:控制字符(如回车 退格 删除等)
2、32到126表示:可以借助于键盘输入并可以显示的字符。48到57代表十个阿拉伯数字;65到90是26个大写英文字母(每个加上32就是对应的小写字母)
ISO Latin-1字符集是Unicode字符集的一个子集,对应于IE4+中Unicode字符指令表的前256个条目。其中Unicode字符为双字节16位,可以表示任何一种语言符号(定长,两字节表示一个字符 65536个);而Latin-1字符集是单字节8位,只能够表示英文和西欧字符。详细内容还请看博客ISO Latin-1字符集。Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。但是这样用在英文类字符身上,是一种极大的资源浪费。于是产生了UTF-8(Unicode的具体实现):是变长编码。英文一个字节,汉字3个字节。
ISO Latin-1字符集是兼容ASCII的,增加了西欧小国的语言字符。
GB2312、GBK、GB18030字符集是兼容上面的ISO Latin-1字符集。这套系统是:英文一个字节,汉字2个字节。
也就是说从0到127就是ASCII的那些字符。GBK全称为 Chinese Internal Code Specification,即汉字内码扩展规范,于 1995 年制定。它主要是扩展了 GB2312,在它的基础上又加了更多的汉字,它一共收录了 21003 个汉字。GB18030 是现在最新的内码字集于 2000 年发布,并于 2001 年强制执行,包含了中国大部分少数民族的语言字符,收录汉字数超过 70000 余个。它主要采用单字节、双字节、四字节对字符编码,它是向下兼容 GB2312 和 GBK 的,虽然是我国的强制使用标准,但在实际生产中很少用到,用得最多的反而是 GBK 和 GB2312。
产生乱码(中文)的原因:编码和解码的字符集系统不一致。windows 操作系统默认的编码是 GBK,Linux 操作系统默认的编码是 UTF-8。当我们用 open()时,调用的是操作系统打开的文件,默认的编码是 GBK。
而上面之所以产生乱码,我们来看一下我们的文件编码的时候是用的哪一套系统?
然后我们点击这个FileEncoding,选择 GBK (进行编码切换)即可。
或者我们直接通过指定文件编码解决中文乱码问题:
# coding=utf-8
fp=open(r"test1.txt","a",encoding="utf-8")
str1="Tsinghua\nUniversity\n"
str2="清华\n大学\n"
fp.write(str1)
fp.write(str2)
fp.close()
因为Python程序执行的时候默认使用Unicode编码,实际存储的时候有可能是utf-8 or GBK(由OS的系统调用决定的,就是写入文件时候的编码)。可是等我们打开这个文件的时候用的是UTF-8来解码的,于是出现乱码(后面我们解码切换成GBK编码就显示中文OK了)。而上面的代码在open里面就指定了写文件时的文件编码,后面再使用UTF-8来打开的时候就没事了。
我们前面也说了,归根到底 文件操作都是由OS来控制的,我们打开的文件对象必须要显式调用close()方法来关闭。
当调用close()的时候,首先会把缓冲区数据写入文件(或者可以直接调用flush()方法),然后关闭文件并释放文件对象。
可是当我们open一个文件对象并进行了操作,若是此时发生异常而导致后面的close()没有去执行。(导致了打开的文件对象没有关闭)于是这个时候就要去结合我们刚学的异常处理机制 finally或者with关键字来实现了。
# coding=utf-8
try:
fp=open(r"test1.txt","a",encoding="utf-8")
str1="Tsinghua\nUniversity\n"
strlist=["清华","大学\n","浙江","大学"]
fp.write(str1)
fp.writelines(strlist)
a=1/0
except BaseException as e:
print("该异常的类型是:",type(e),";","该异常是:",e)
finally:
fp.close()
下面我们来看一下在Python里面更加常用的一种机制:with上下文管理:可以自动的管理上下文资源。(不论什么原因离开with块,都能够保证文件被正常的关闭。并且可以在代码块执行完成之后自动还原到进入该代码块是的现场)
# coding=utf-8
str1="Tsinghua\nUniversity\n"
strlist=["清华","大学\n","浙江","大学"]
with open(r"test1.txt","a",encoding="utf-8") as fp:
fp.write(str1)
fp.writelines(strlist)
这样使用起来确实更加简便!
文件读取一般使用到如下三个方法:
#读取一个文件的前10个 字 符:
#一次性将文件内容读到程序中:
#按行读取一个文件
#使用迭代器(每次返回一行)读取文本文件
之所以在程序里面使用end=""
,因为我们的文件里面已经有一个换行符了(上面的那个打印前10个字符也看到了)。
下面我们先看一下enumerate函数:
# coding=utf-8
with open(r"test1.txt","r",encoding="utf-8") as iterator_f:
a=enumerate(iterator_f)
print(a,type(a))
b=list(a)
print(b)
操作文件每行内容前面都加上 行号 |
# coding=utf-8
with open(r"test1.txt","r",encoding="utf-8") as iterator_f:
a=enumerate(iterator_f)
print(a,type(a))
b=list(a)
print(b)
print(a)
str=["*"+str(index)+" "+value for index,value in b]
print(str)
with open(r"test2.txt","a",encoding="utf-8") as fp:
fp.writelines(str)
二进制文件的处理流程和文本文件的一样:
1、创建文件对象(需要指定二进制模式)
2、创建好之后就可以使用write()、read()实现文件的读写操作
下面我们就来看一个实例:
# 读取一个图片,然后实现该文件的拷贝
这是我们的一张图片,下面我们来利用Python的文件操作再复制一张:
# coding=utf-8
# rb模式是 创建一个可读的二进制文件对象
with open("Tsinghua.jpg","rb") as fr:
with open("Tsinghua_Of_Copy.jpg","wb") as fw:
for a_line_of_file in fr.readlines():
fw.write(a_line_of_file)
print("文件拷贝完成!")
文件对象封装了文件相关的操作,下面我们就来列举一下文件对象的常用属性和方法:
在Python里面,一切皆对象 对象在本质上就是一个“存储数据的内存块”。可是有时候我们需要将这个“内存块上的数据”保存到硬盘之上 or 借助于网络传送到其他的地方。这个时候 就要使用“对象的序列化和反序列化”,那么什么是序列化和反序列化呢?
序列化:将对象转化成 “串行化”的数据形式,保存到硬盘之上 or 借助于网络传送到其他的地方
反序列化:反过程 将读取到的“串行化”数据 转化成对象
具体的方法使用如下:
# 序列化
pickle.dump(obj,file) #obj是被序列化的对象,file是存储的文件
# 反序列化
pickle.load(file) #从file里面读取数据,然后反序列化成对象
注:此时的str对象 和 str1对象是两个完全不同的对象。(仅仅是内容一样而已)
CSV:(Comma Separated Values)逗号分隔符文本格式
常用于 数据交换、Excel文件和数据库数据的导出导入等;不同于Excel文件的是,CSV文件有:
1、值没有类型,所有值都是字符串
2、不能指定字体 颜色等样式
3、不能指定单元格的 宽和高,不能合并单元格
4、没有多个工作表
5、不能嵌入 图像图表等
CSV文件的操作使用的是Python标准库里面的csv模块,它提供了对于csv格式文件对象的 读写方法。
下面来看一下,我们有这么一个表格文件 内容如下:
我们把它转成CVS格式的文件如下:
下面我们首先看一下CSV文件的读取操作:
下面看一下往一个CSV里面写内容 (写入的内容是list对象):
os模块可以帮助我们直接对OS进行操作,即:可以直接调用OS的可执行程序、命令来对文件 目录来进行操作!(大家熟悉Linux开发的话,就相当于我们在C语言里面调用的system(cmd)
来执行一个脚本or命令)
具体的实例如下:(调用一下Windows的程序等)
或者是打开我们的CMD,如下(被重定向到控制台):
OK,下面我们直接调用可执行的文件(提前得安装OK的!例如 QQ):
os.startfile(r"D:\360安全浏览器下载\Tencent\QQ\Bin\QQScLauncher.exe")
除了之前学的文件对象操作来实现对文件内容的读写,我们还可以使用这里的os和os.path模块来实现文件和目录的操作。
os模块 文件操作方法:
方法 | 描述 |
---|---|
remove(path) | 删除一个指定的文件 |
rename(src,dest) | 重命名文件或者目录 |
stat(path) | 返回文件的所有属性 |
listdir(path) | 返回path目录下的文件和目录列表 |
os模块 目录操作方法:
方法 | 描述 |
---|---|
mkdir(path) | 创建目录 |
makedirs(path1/path2/- - -) | 创建多级目录 |
rmdir(path) | 删除目录 |
removedirs(path1/path2/- - -) | 删除多级目录 |
getcwd() | 返回当前工作目录 |
chdir(path) | 把path作为当前工作目录 |
walk() | 遍历目录树 |
sep() | 当前OS所使用的路径分隔符 |
注:相对路径都是相对于当前的工作目录而言的!
也就是说chdir
之后,工作目录已经变了。对于某些删除操作,只有在不使用该目录的时候完成!
这个removedirs
是只可以删除空的目录,若是目录下面有内容 则报错:OSError
此时我们想用listdir
来返回当前目录下的文件和目录列表:
在实际的操作中,掌握了一个确定而又准确的path 就可以完成很多的后续操作。而路径path又是一个操作的难点,因此Python为我们提供了关于路径的一些方法(路径判断、路径切分、路径连接和目录遍历等)如:
1、isabs(path):判断路径是否为绝对路径
2、isdir(path):路径是否为目录
3、isfile(path):路径是否为文件
4、exists(path):指定路径的文件是否存在
5、getsize(filename):得到文件大小
6、abspath(path):返回绝对路径
7、dirname ( p):返回目录的路径
8、getatime(filename):得到文件的最新访问时间
9、getctime(filename):得到文件的创建时间
10、getmtime(filkename):得到文件的最新修改时间
11、walk(top,func,arg):递归的方式遍历目录
12、join(path,*paths):连接多个路径
13、split(path):对该路径进行切分,最后以元组形式返回
14、splitext(path):从路径中分割得到文件的扩展名
#coding=utf-8
'''
Author :SongBaoBao
Project :untitled
filename:mypath.py
currtime:2020/6/25--9:47
commpany:Tsinghua University
mycsdnis:https://blog.csdn.net/weixin_43949535
yylolLPL:Royal Never Give Up
'''
# 测试os.path模块的路径和目录操作
import os
import time
# import os.path
from os import path
currentpath=os.getcwd()
print("当前的目录currentpath为:",currentpath)
print("当前Python文件的绝对路径为:",path.abspath("mypath.py"))
print("currentpath是不是绝对路径:",path.isabs(currentpath))
print("currentpath是不是目录路径:",path.isdir(currentpath))
print("currentpath是不是文件路径:",path.isfile(currentpath))
# 拼接当前Python文件的绝对路径
thisfilepath=path.join(currentpath,"mypath.py")
print("当前的文件mypath.py是否存在:",path.exists(thisfilepath))
print("当前的文件mypath.py文件大小:",path.getsize(thisfilepath),"byte")
print("当前Python文件的所在目录为:",path.dirname(thisfilepath))
# 当前Python文件的时间信息
print("当前文件的创建时间是:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(path.getctime(thisfilepath))))
print("当前文件的访问时间是:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(path.getatime(thisfilepath))))
print("当前文件的修改时间是:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(path.getmtime(thisfilepath))))
结果显示为:
E:\Tsinghua_University\Python_Study\untitled\venv\Scripts\python.exe E:/Tsinghua_University/Python_Study/untitled/venv/src/mypath.py
当前的目录currentpath为: E:\Tsinghua_University\Python_Study\untitled\venv\src
当前Python文件的绝对路径为: E:\Tsinghua_University\Python_Study\untitled\venv\src\mypath.py
currentpath是不是绝对路径: True
currentpath是不是目录路径: True
currentpath是不是文件路径: False
当前的文件mypath.py是否存在: True
当前的文件mypath.py文件大小: 1444 byte
当前Python文件的所在目录为: E:\Tsinghua_University\Python_Study\untitled\venv\src
当前文件的创建时间是: 2020-06-25 09:45:55
当前文件的访问时间是: 2020-06-25 10:22:41
当前文件的修改时间是: 2020-06-25 10:22:41
Process finished with exit code 0
下面来看一个比较重要的方法:os.walk()
来递归的遍历所有的文件:
os.walk()
---------
返回一个三个元素的tuple:(dirpath,dirnames,filenames)
-------------------------------------------------
一、该指定的目录路径
二、目录下的所有目录
三、目录下的所有文件
#coding=utf-8
'''
Author :SongBaoBao
Project :untitled
FileName:mywalk.py
Currtime:2020/6/25--13:40
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
# 测试walk方法来递归的遍历文件
import os
from os import path
curpath=os.getcwd()
print(curpath)
# 来遍历src目录
file_list_of_src=os.walk("../..")
print(file_list_of_src)
print("*"*127)
i=0
for dirpath,dirnames,filenames in file_list_of_src:
this_abspath=path.abspath(dirpath)
print("\033[1;34m ","一个目录为:",format(dirpath," <25"),";","其绝对路径为:",this_abspath,"\033[0m")
for filename in filenames:
print("\033[1;35m ","一个文件名字为:", format(filename, " <22"), ";", "其绝对路径为:", path.join(this_abspath,filename),"\033[0m")
for dirname in dirnames:
print("\033[1;33m ", "一个目录名字为:", format(dirname, " <22"), ";", "其绝对路径为:", path.join(this_abspath,dirname), "\033[0m")
i+=1
print("*"*127," ","第",i,"次遍历结束",";当前正在遍历的目录是:",this_abspath)
OK,那我们来看一下程序的结果:
同样的代码,我们这里多增加几个目录,其结果如下:
注:从结果来看,这是一个深度优先遍历的操作!
OK,那我们下面来看一下递归算法:
递归算法:基本思想就是自己调用自己 (降低问题的规模直到可以直接进行计算的递归基,然后反向回溯解决问题)
-------------------------------------------------------------------------------------------------------------------------------------------------------
适用的场景:汉诺塔 斐波那契数字 快速排序等
递归的定义:(凡是可以递归解决的都也能够使用循环完成)
一、递归基:递归结束的条件(递归一般会有边界条件)
二、递归体:什么时候需要调用自身方法
三、两思路:分而治之 减而知之
下面来看一下两个实例:
下面来利用递归算法来展示一个目录的树结构: (下面看一下背景:)
下面来看一下它的打印结果:
解释一下:
1、通过
os.listdir(path)
得到了path下面所有文件和目录的名字
2、通过os.path.join(path,filename)
得到他们的相对路径
3、for循环遍历这个 文件名的集合,判断这个相对路径下面是否为文件还是目录
4、文件则直接打印;是目录则去递归该相对路径为path
虽然是上面我使用了颜色用以区分文件和目录,但是层级结构没有打印出来 修改如下:
#coding=utf-8
'''
Author :SongBaoBao
Project :untitled
FileName:myrecursion_tree.py
Currtime:2020/6/26--14:22
Commpany:Tsinghua University
MyCsdnIs:https://blog.csdn.net/weixin_43949535
MyLolLpl:Royal Never Give Up
'''
#
# 利用递归实现目录树结构:递归打印所有的目录和文件
#
import os
def getAllFiles(path,level):
# 获得子目录和子文件 文件名
childFiles=os.listdir(path)
for filename in childFiles:
# 得到它的相对路径
hisRelativePath=os.path.join(path,filename)
if os.path.isdir(hisRelativePath):
print("\t"*level,"第",level,"级:","\033[1;34m ", "它是一个目录为:", format(hisRelativePath, " <25"), ";", "其绝对路径为:", os.path.abspath(hisRelativePath), "\033[0m")
getAllFiles(hisRelativePath,level+1)
else:
print("\t"*level,"第",level,"级:","\033[1;35m ", "一个文件名字为:", format(hisRelativePath, " <25"), ";", "其绝对路径为:",os.path.abspath(hisRelativePath), "\033[0m")
getAllFiles("../../../src",0)
shutil模块也是Python标准库里面提供的,主要是用于文件和目录的 cp,mv和rm等。当然还是可以做文件和目录的压缩 与 解压操作。 shutil模块是作为对os模块的一个补充。
来先看一下开始时候的状态:
然后执行拷贝文件和目录之后的结果如下:
上面是一个简单的文件和目录的cp操作(当然若是文件提前就存在了,则是会报错的)
下面我们来看一个压缩和解压缩的操作:(当前状态是没有的)
OK,下面来看一下结果: