文件操作(IO技术)
文章目录
1 为什么要使用IO技术
前面写的程序数据都没有进行实际的存储,因此 python 解释器执行完数据就消失了。实际开发中,我们经常需要从外部存储介质(硬盘、光盘、U 盘等)读取数据,或者将程序产生的数据存储到文件中,实现“持久化”保存。
2 文本文件和二进制文件
按文件中数据组织形式,我们把文件分为文本文件和二进制文件两大类。
(1)文本文件
文本文件存储的是普通“字符”文本,python 默认为 unicode 字符集(两个字节表示一个字符,最多可以表示:65536 个),可以使用记事本程序打开。但是,像 word 软件编辑的文档不是文本文件。
(2)二进制文件
二进制文件把数据内容用“字节”进行存储,无法用记事本打开。必须使用专用的软件解码。常见的有:MP4 视频文件、MP3 音频文件、JPG 图片、doc 文档等等。
可以这么记,能用记事本打开的就是文本文件,其他都是二进制文件。
3 创建文件对象
python中万物皆对象,因此文件也可以看成是对象,在读写文件之前,必须先创建文件对象。
创建文件对象的格式:
open(文件名[,打开方式])
文件名可以录入全路径,比如“D:\a\b.txt”,也可以用相对路径,即“b.txt”,即与源程序同一个文件夹下。如为了减少“\”的输入,可以在路径前面加 r 表示不转义。
如:f = open(r"d:\b.txt",“w”)
打开方式有以下几种:
如果我们没有增加模式“b”,则默认创建的是文本文件对象,处理的基本单元是“字符”。如果是二进制模式“b”,则创建的是二进制文件对象,处理的基本单元是“字节”。
4 文本文件的写入
文本文件的写入一般就是三个步骤:创建文件对象、写入数据、关闭文件。
这里写一个简单的测试程序:
f = open(r"a.txt","a") # 创建文件对象
f.write("itbaizhan\nsxt\n") # 写入数据
f.close() # 关闭文件
5 常用编码与中文乱码问题
(1)常用编码
最初的编码系统是ASCII,这是单字节编码系统,最高位都是0,不同字节只有后面 7 位不一样,也就是说,这套编码系统实际只使用了7位,只能表示 128 个字符。
由于ASCII只能表示英文,而且最高位没有利用,所以后面诞生了一个新的编码系统,即ISO-8859-1,是一个 8 位单字节字符集,它把 ASCII 的最高位也利用起来,它可以表示西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,并兼容了 ASCII,但它并没有完全用完。
GB2312,GBK,GB18030是由中国大陆开发的字符编码系统,它们都兼容ISO-8859-1,它使用一个字符表示英文,两个字符表示中文。
由于ASCII和ISO-8859-1未能包含所有国家的文字,各国都推出了支持本国文字的字符编码系统,但编码系统不统一,给不同国家之间的信息交换造成不变,因此诞生了统一的文字编码系统,即Unicode。Unicode设计成了固定两个字节,所有的字符都用 16 位。
Unicode由于英文字符也占8位,造成了空间的浪费,并且Unicode 完全重新设计,不兼容 iso8859-1,也不兼容任何其他编码,影响了该编码系统的顺利推广。
由于unicode 不便于传输和存储,因此而产生了 UTF 编码,UTF-8 全称是(8-bit Unicode Transformation Format)。UTF 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符,不过,UTF 编码是不定长编码,每一个字符的长度从 1-4 个字节不等。其中,英文字母都是用一个字节表示,而汉字使用三个字节。
(2)中文乱码问题
windows 操作系统默认的编码是 GBK(Linux 操作系统默认的编码是 UTF-8),当我们用 open()时,是调用操作系统打开文件,因此默认是用 GBK对文件进行读写,而如果使用UTF-8打开,这样就会出现乱码问题,归根结底,就是编码和解码使用的字符集不一样。(python解释器执行程序时,虽然用的是Unicode编码,但在Windows系统中存储和传输时再重新编码成GBK)
比如,在调用open的时候,我设定字符集为utf-8,那么就是按照utf-8读写字符集。
f = open(r"b.txt","w",encoding="utf-8")
f.write("尚学堂\n百战程序员\n")
f.close()
在该项目下出现了一个新的文件,即b.txt,使用终端命令读取文件,就会产生乱码,因为Windows默认使用的是GBK字符集,即用utf-8编码,用GBK解码(使用终端命令读取文件就是“解码”的过程)。
如果在调用调用open的时候不设置,那么将默认使用GBK进行读写,即编码和解码使用的是同一套字符集。
f = open(r"b.txt","w")
f.write("尚学堂\n百战程序员\n") # 默认使用gbk打开文件
f.close()
再次使用终端命令读取文件
英文没有乱码问题,因为无论是utf-8(linux系统),还是gbk(Windows系统),都是兼容ASCII字符集的。
(3)write()/writelines()写入数据
write(a):把字符串 a 写入到文件中
writelines(b):把字符串列表写入文件中,自动添加换行符
try:
f = open(r"c.txt","w")
strs = ["aa\n","bb\n","cc\n"] # 如果不添加换行符,那么这几个字符创将挤在同一行
f.writelines(strs)
except BaseException as e:
print(e)
finally:
f.close()
结果
6 close()关闭文件流
由于文件底层是由操作系统控制,所以我们打开的文件对象必须显式调用 close()方法关闭文件对象。当调用 close()方法时,首先会把缓冲区数据写入文件(也可以直接调用 flush()方法),再关闭文件,释放文件对象。
python程序打开一个文件,此文件在硬盘上,python程序需要调用外部操作系统,由操作系统对硬盘进行读写,程序运行结束,操作系统在读写文件时打开的资源必须全部关闭,因此必须调用close()方法。
为了确保打开的文件对象正常关闭,一般结合异常机制的 finally 或者 with 关键字实现无论何种情况都能关闭打开的文件对象。
如使用异常机制的话,把close()方法写到 finally 中去,如果用的是 with 上下文管理,那么无需显示调用close()方法。
使用try—finally结构
try:
f = open(r"c.txt","w")
strs = ["aa\n","bb\n","cc\n"]
f.writelines(strs)
except BaseException as e:
print(e)
finally:
f.close()
使用with上下文管理
with open(r"d.txt","a") as f:
f.write("gaoqi,i love u!")
with 关键字(上下文管理器)可以自动管理上下文资源,不论什么原因跳出 with 块,都能确保文件正确的关闭,并且可以在代码块执行完毕后自动还原进入该代码块时的现场(这里的还原,就相当于关闭所有在 with 块中打开的资源)。
7 文本文件的读取
文件的读取一般使用如下三个方法:
(1)read([size])
这里[size]不是列表,而是表示size可以省略。
从文件中读取 size 个字符,并作为结果返回。如果没有size 参数,则读取整个文件,读取到文件末尾时,会返回空字符串。
with open(r"e.txt","r",encoding="utf-8") as f:
str = f.read(3) # 表示读取三个字符
print(str)
输出
我lo
(2)readline()
读取一行内容作为结果返回。读取到文件末尾,会返回空字符串。
with open(r"a.txt","r") as f:
while True:
fragment = f.readline() # 一行一行地读
if not fragment:
break # 读到最后 fragment 为空,跳出循环
else:
print(fragment,end="")
输出
itbaizhan
sxt
itbaizhan
sxt
(3)readlines()
文本文件中,每一行作为一个字符串存入列表中,返回该列表
with open("e.txt","r",encoding="utf-8") as f:
lines = f.readlines()
print(lines)
输出
['我love u! #1\n', '尚学堂 #2\n', '百战程序员 #3\n']
(4)把文件当成迭代器进行遍历
with open("e.txt","r",encoding="utf-8") as f:
for a in f: # 遍历文件
print(a,end="")
输出
我love u! #1
尚学堂 #2
百战程序员 #3
8 enumerate()
enumerate方法生成的是枚举对象,当要同时遍历一个可迭代序列的索引和元组的时候,使用enumerate是很方便的。
执行下列程序,体会enumerate的用法
str = 'abcd'
for index, char in enumerate(str): # 同时遍历索引和元素
print(index, char)
for tu in enumerate(str): # 循环没执行一次,enumerate方法就生成一个元组
print(tu)
输出
0 a
1 b
2 c
3 d
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
练习,为文本文件 e.txt 的每一行添加行号,比如第一行就在末尾添加“# 1”,e.txt 的初试状态如下:
with open("e.txt","r",encoding="utf-8") as f:
lines = f.readlines()
lines = [ line.rstrip()+" #"+str(index+1)+"\n" for index,line in enumerate(lines)] # 推导式生成列表
with open("e.txt","w",encoding="utf-8") as f:
f.writelines(lines)
结果
rstrip是去掉字符串末尾的指定字符,默认是空格,执行下面的程序,体会它的用法
str = " this is string example....wow!!! "
print("原来长度", len(str))
str = str.rstrip() # 未指定删除什么,那就删除空格
print(str)
print("现在长度", len(str))
print("+"*50)
str = "88888888this is string example....wow!!!8888888"
print("原来长度", len(str))
str = str.rstrip('8')
print(str)
print("现在长度", len(str))
输出
原来长度 42
this is string example....wow!!!
现在长度 37
++++++++++++++++++++++++++++++++++++++++++++++++++
原来长度 47
88888888this is string example....wow!!!
现在长度 40
9 二进制文件的读写
二进制文件的处理流程和文本文件流程一致。首先还是要创建文件对象,不过,我们需要指定二进制模式(也就是在w、r、a的后头加 b),从而创建出二进制文件对象。
如,把 aa.gif 拷贝到 aa_copy.gif 中
with open("aa.gif","rb") as f:
with open("aa_copy.gif","wb") as w:
for line in f.readlines(): # 每次读一行
w.write(line)
print("图片拷贝完成.........")
10 序列化与反序列化
Python 中,一切皆对象,对象本质上就是一个“存储数据的内存块”。有时候,我们需要将“内存块的数据”保存到硬盘上,或者通过网络传输到其他的计算机上。这时候,就需要“对象的序列化和反序列化”。
序列化指的是:将对象转化成“串行化”数据形式,相当于给数据编号,存储到硬盘或通过网络传输到其他地方。
反序列化是指相反的过程,将读取到的“串行化数据”转化成对象。
序列化和反序列化需要导入pickle模块,序列化用 pickle.dump(obj, file),obj 就是要被序列化的对象,file 指的是存储的文件;反序列化用 pickle.load(file) 从 file 读取数据,反序列化成对象
import pickle
a1 = "高淇"
a2 = 234
a3 = [10,20,30,40]
with open("data.dat","wb") as f:
pickle.dump(a1,f)
pickle.dump(a2,f)
pickle.dump(a3,f)
with open("data.dat","rb") as f:
b1 = pickle.load(f);
b2 = pickle.load(f);
b3 = pickle.load(f)
print(b1);print(b2);print(b3) # 一行写多条语句,要用分号隔开
print(id(a1));print(id(b1))
# 看一下反序列化后的对象与原对象的地址是否相同
输出
高淇
234
[10, 20, 30, 40]
39643728
40175728
11 csv文件的操作
csv(Comma Separated Values)是逗号分隔符文本格式,常用于数据交换、Excel文件和数据库数据的导入和导出。
与 Excel 文件不同,CSV 文件中:
(1)值没有类型,所有值都是字符串;
(2)不能指定字体颜色等样式;
(3)不能指定单元格的宽高,不能合并单元格;
(4)没有多个工作表;
(5)不能嵌入图像图表
Python 标准库的模块 csv 提供了读取和写入 csv 格式文件的对象。
我们在 excel 中建立一个简单的表格:
然后另存为"csv(逗号分隔)",我们打开查看这个 csv 文件内容:
分别使用 csv.reader 和 csv.writer 方法读取和写入
也可以使用
import csv
with open("dd.csv","r") as f:
a_csv = csv.reader(f)
# 创建 csv.reader 对象,它是一个包含所有数据的列表,每一行为一个元素
for row in a_csv: # 循环打印各行内容
print(row)
# 和zip对象类似,csv.reader对象也是只能用一次,遍历完之后,就无效了
with open("ee.csv","w") as f:
b_csv = csv.writer(f) # 创建 csv.writer 对象
b_csv.writerow(["ID","姓名","年龄"]) # 调用 writerow 函数将列表中的元素写入 csv 文件,每次写一行
b_csv.writerow(["1001","高淇","18"])
c = [["1002","希希","3"],["1003","东东","4"]]
b_csv.writerows(c) # 每个子列表分别作为一行,一次性写入多行
输出
['ID', '姓名', '年龄', '薪资']
['1001', '高淇', '18', '50000']
['1002', '高八', '19', '30000']
['1003', '高九', '20', '20000']
ee.csv 中的内容为
11 os模块
os 模块可以帮助我们直接对操作系统进行操作。我们可以直接调用操作系统的可执行文件、命令,直接操作文件、目录等等。是系统运维的核心基础。
(1)os 模块-调用操作系统命令
·os.system 可以帮助我们直接调用系统的命令
os.system(“notepad.exe”) 引号内输入命令和在下面截图的方框输入命令的效果是一致的。
(2)os.startfile:直接调用可执行文件
os.startfile(“D:\Program Files (x86)\Tencent\WeChat\WeChat.exe”),可以打开微信。
(3)文件和目录操作
执行下面程序,了解文件和目录的相关操作,了解即可,以后用到了再具体学
#coding=utf-8
#测试os模块中,关于文件和目录的操作
import os
#############获取文件和文件夹相关的信息################
# print(os.name) # 返回操作系统的名字,windows->nt linux和unix->posix
# print(os.sep) # 返回分隔符,windows->\ linux和unix->/
# print(repr(os.linesep)) # 返回换行符 windows->\r\n linux-->\n\
# repr函数用来取得对象的规范字符串表示,返回一个字符串,与str()类似
# repr() 与 str() 的区别是什么,这个暂时先不管,以后需要区分的时候再去搞明白
# print(os.stat("my02.py")) # 获得文件的信息
##############关于工作目录的操作###############
# print(os.getcwd())
# os.chdir("d:") # 改变当前的工作目录为:d:盘根目录
# os.mkdir("书籍") # 创建目录,这里用的是相对路径,
# 这条语句执行之后,将在D盘下新建“书籍”,因为前面已经修改了工作空间
################创建目录、创建多级目录、删除#############
# os.mkdir("书籍") # 在当前工作空间中创建一个文件夹
# os.rmdir("书籍") # 删除目录,相对路径都是相对于当前的工作目录
# os.makedirs("电影/港台/周星驰") # 创建多级目录
# os.removedirs("电影/港台/周星驰") # 删除工作目录,只能删除空目录
# os.makedirs("../音乐/香港/刘德华") # 在上一级目录中创建多级目录,../指的是上一级目录
# os.rename("电影","movie") # 修改文件或文件夹的名字
dirs = os.listdir("movie") # 以列表的形式返回一级子目录和子文件
print(dirs)
#############################
遇到一个新的模块的时候,不是把模块中的方法都过一遍,而是把暂时用得着的先学会,其他的部分将来用的时候再学,以建立只是体现为先。
(4)os.path 模块
os.path 模块提供了目录相关(路径判断、路径切分、路径连接、文件夹遍历)的操作
执行下面程序,了解 os.path 简单的使用,了解即可,以后用到了再具体学
# coding=utf-8
# 测试os.path中关于目录、路径的操作
import os
import os.path #from os import path
############判断:绝对路径、是否目录、是否文件、文件是否存在#############
# print(os.path.isabs("d:/a.txt")) # 判断是否为绝对路径
# print(os.path.isdir("d:/a.txt")) # 判断是否为目录(False)
# print(os.path.isfile("d:/a.txt")) # 判断是否为文件(True)
# print(os.path.exists("d:/a.txt")) # 判断文件或文件夹是否存在
##############获得文件基本信息################
# print(os.path.getsize("b.txt")) # 返回文件的大小
# print(os.path.abspath("b.txt")) # 返回绝对路径
# print(os.path.dirname("d:/a.txt")) # 返回目录的路径
# print(os.path.getctime("b.txt")) # 返回文件的最后访问时间
# print(os.path.getatime("b.txt")) # 返回文件的最后修改时间
# print(os.path.getmtime("b.txt")) #
##############对路径的操作#####################
path = os.path.abspath("b.txt")
print(os.path.split(path)) # 对文件进行分割,以列表形式返回
print(os.path.splitext(path)) # 从路径中分割文件的扩展名
print(os.path.join("aa","bb","cc")) # 将几个字符串用路径分隔符链接起来
输出
('F:\\拜师培训\\3350_Python编程-pycharm版\\第十节文件处理课程源码和资料\\mypro_io(本章源代码)\\test_os', 'b.txt')
('F:\\拜师培训\\3350_Python编程-pycharm版\\第十节文件处理课程源码和资料\\mypro_io(本章源代码)\\test_os\\b', '.txt')
aa\bb\cc
练习,列出指定目录下所有的.py 文件,并输出文件名
#coding=utf-8
# 列出工作目录下所有的.py文件,并输出文件名
import os
path = os.getcwd()
file_list = os.listdir(path) #列出子目录、子文件
for filename in file_list:
if filename.endswith("py"): # endswith("py") 以 py 结尾的字符串则返回True
print(filename,end="\t")
print("###############")
# 使用列表推导式
file_list2 = [filename for filename in os.listdir(path) if filename.endswith("py") ]
for f in file_list2:
print(f,end="\t")
输出
my01.py my02.py my03.py my04.py my05.py ###############
my01.py my02.py my03.py my04.py my05.py
(5)os.walk()递归遍历所有文件和目录
os.walk()方法:返回一个生成器对象,用for遍历该生成器对象,每次返回 3 个元素的元组,(dirpath, dirnames, filenames),
dirpath:要列出指定目录的路径;
dirnames:目录下的所有文件夹;
filenames:目录下的所有文件
执行下面的程序,观察os.walk()方法的使用。
#coding=utf-8
#测试os.walk()递归遍历所有的子目录和子文件
import os
all_files = []
path = os.getcwd()
list_files = os.walk(path)
print(type(list_files)) # list_files 是一个生成器对象
for dirpath, dirnames, filenames in list_files:
for dir in dirnames:
all_files.append(os.path.join(dirpath,dir)) # 将工作空间和子目录进行拼接
for file in filenames:
all_files.append(os.path.join(dirpath,file)) # 将工作空间和子文件进行拼接
# 打印所有的子目录和子文件
for file in all_files:
print(file)
12.拷贝和压缩
(1)拷贝
shutil 模块是 python 标准库中提供的,主要用来做文件和文件夹的拷贝、移动、删除等;还可以做文件和文件夹的压缩、解压缩操作。
os 模块提供了对目录或文件的一般操作。shutil 模块作为补充,提供了移动、复制、压缩、解压等操作,这些 os 模块都没有提供。
(2)压缩
有两种压缩方式,一种是使用shutil模块,另一种是是使用 zipfile 模块
#coding=utf-8
#测试shutil模块的用法:拷贝、压缩
import shutil
import zipfile
shutil.copyfile("1.txt","1_copy.txt") # 拷贝文件,如果1_copy.txt已经存在,那就覆盖
# shutil.copytree("movie/港台","电影") # 拷贝目录,"电影"目录不存在时才能正常拷贝
# shutil.copytree("movie/港台","电影",ignore=shutil.ignore_patterns("*.txt","*.html"))
# ignore表示要忽略的文件,这里 * 表示通配符,即拷贝的时候,不拷贝txt和html后缀的文件
# 压缩、解压缩
# shutil.make_archive("电影/gg","zip","movie/港台")
# 把"movie/港台"压缩到"电影/gg"中,压缩文件的后缀名为".zip"
# 最后生成了一个名为 “gg.zip” 的压缩文件
z1 = zipfile.ZipFile("d:/a.zip","w") # 使用 zipfile 模块,先建立一个压缩对象,然后再写入,类似于文件操作
z1.write("1.txt")
z1.write("1_copy.txt")
z1.close() # 关闭压缩对象
z2 = zipfile.ZipFile("d:/a.zip","r") # 建立一个解压缩对象
z2.extractall("电影") # 把压缩包解压到电影目录中
z2.close() # 关闭压缩对象
练习:递归算法打印目录树
# coding=utf-8
# 递归打印所有的目录和文件
import os
allfiles = []
def getAllFiles(path,level):
childFiles = os.listdir(path) # 以列表的形式返回一级子目录和文件
for file in childFiles: # 遍历列表
filepath = os.path.join(path,file) # 将文件/文件夹名(file)与路径名(path)连接
if os.path.isdir(filepath): # 如果是文件夹,则调用本方法
getAllFiles(filepath,level+1)
allfiles.append("\t"*level+filepath) # 不管是文件还是文件夹,都追加到列表中
getAllFiles("test_os",0)
for f in allfiles:
print(f)
输出
test_os\b.txt
test_os\movie\大陆
test_os\movie\尚学堂.mp4
test_os\movie\日本
test_os\movie\港台\周星驰\功夫.mp4
test_os\movie\港台\周星驰
test_os\movie\港台
test_os\movie
test_os\my01.py
test_os\my02.py
test_os\my03.py
test_os\my04.py
test_os\my05.py
os.walk()方法的底层,其实也是调用了递归方法。