第八章 文件操作(IO技术)
一个完整的程序一般都包括数据的存储和读取;我们在前面写的程序数据都没有进行实际的存储,因此python解释器执行完数据就消失了,实际开发中,我们经常需要从外部存储介质(硬盘、光盘、U盘等)读取数据,或者将程序产生的数据存储到文件中,实现“持久化”保存
很多软件系统是将数据存储在数据库中;数据库实际也是基于文件形式存储的,本章我们就学习文件的相关操作。
文本文件和二进制文件
按文件中数据组织形式,我们把文件分为文本文件和二进制文件两大类。
1.文本文件
文本文件存储的是普通“字符”文本,默认为unicode字符集(两个字节表示一个符号,最多可以表示:65536(2的16次方)个,足够表示所有的语言),可以使用记事本程序打开,但是,像word软件编辑的文档不是文本文件。(以字符为单位)
2.二进制文件
二进制文件把数据内容用“字节”进行存储,无法用记事本打开,必须使用专用的软件解码,常见的有:MP4音频文件、MP3音频文件、JPG图片、doc文档等等。(以字节为单位)
文件操作相关模块概述
Python标准库中,如下文件操作相关的模块,我们会陆续介绍学习。
名称 |
说明 |
io模块 |
文件流的输入和输出操作 (input output) |
os模块 |
基本操作系统功能,包括文件操作 |
glob模块 |
查找符合特定规则的文件路径名 |
fnmatch模块 |
使用模式来匹配文件路径名 |
fileinput模块 |
处理多个输入文件 |
filecmp模块 |
用于文件的比较 |
cvs模块 |
用于cvs文件处理 |
pickle和cPickle |
用于序列化和反序列化 |
xml包 |
用于xml数据处理 |
bz2、gzip、zipfile、zlib、trafile |
用于处理压缩和解压缩文件(分别对应不同算法) |
创建文件对象open()
open()函数用于创建文件对象(python中一切皆对象),基本语法格式如下:
open[文件名[,打开方式]]
如果只是文件名,代表在当前目录下的文件。文件名可以录入全路径,比如
D:\a\b.txt。为了减少“\”的输入,可以使用原始字符串:r“d:\b.txt”。
示例如下:
f = open(r”d:\b.txt”,”a”)
打开方式有如下几种:
模式 |
描述 |
r |
读read模式(把文件内容读入) |
w |
写write模式,如果文件不存在则创建;如果文件存在,则重新写内容 |
a |
追加模式,如果文件不存在则创建,如果文件存在,则在文件末尾追加内容 |
b |
二进制模式(可与其他模式组合使用) |
+ |
读、写模式(可与其他模式组合使用) |
文本文件对象和二进制对象的创建:
如果我们没有增加模式“b”,则默认创建的是文本文件对象,处理的基本
单元是“字符”。如果是二进制模式“b”,则创建的是二进制文件对象,处理
的基本单元是“字节”
例:f = open(r”d\b.jpg”,”bw”) 处理的是二进制文件
文本文件的写入
基本的文件写入操作
文本文件的写入一般就是三个步骤:
1.创建文件对象
2.写入数据
3.关闭文件对象
Python文件要把数据写入硬盘中,python文件由解释器执行,解释器运行于操
作系统os(中),操作系统直接操作硬盘。python文件把数据给解释器,解释
器给操作系统,操作系统给硬盘。因为过程中也调用了操作系统的资源,所以数
据处理完后要关闭解释器和操作系统打开的文件资源
我们首先创建一个小程序,体验一下文本文件的写入操作。
【操作】文本写入操作简单测试
f= open(r"d:\a.txt","a")
s = '尚学堂\n百战程序员\n'
f.write(s)
f.close()
结果:
常用编码介绍
在操作文本文件时,经常会操作中文,这时候就经常会碰到乱码问题。为了让大
家有能力解决中文乱码问题,这里简单介绍一下各种编码问题。
常用编码之间关系如下
一个字节8位,可以表示2^8 = 256个字符
0-31表示控制字符如回车、退格、删除等;32-126表示打印字符既可以通过键盘输入并且能显示出来的字符;其中48-57位0到9十个阿拉伯数字,65-90位26个大写英文字母,97-122号为26个小写英文字母,其余为一些标点符号、运算符号,具体可以参考ASCII标准表。
windows操作系统默认的编码是GBK,Linux操作系统默认的编码是UTF-8。当我们用open()时,调用的是操作系统打开的文件,默认的编码是GBK。
【示例】中文字符文件,乱码出现测试
#测试写入中文
f = open(r"b.txt","w")
f.write("尚学堂\n百战程序\n")
f.close()
结果:
【示例】可以直接用编码
#测试写入中文
f = open(r"b.txt","w",encoding='utf-8')
f.write("尚学堂\n百战程序\n")
f.close()
write(a):把字符串a写入到文件中
writelines(b):把字符串列表写入文件中,不添加换行符
【操作】添加字符串列表数据到文件中
#测试写入中文
f = open(r"d:\a.txt","w",encoding='utf-8')
s = {'高崎\n','高老三\n','高老四\n'}
f.writelines(s)
f.close()
结果:
由于文件底层是由操作系统控制,所以我们打开的文件对象必须显示调用close()方法关闭文件对象,当调用close()方法时,首先会把缓冲区数据写入文件(也可以直接调用flush()方法),再关闭文件,释放文件对象。
为了确保打开的文件对象正常关闭,一般结合异常机制的finally或者with关键字实现无论何种情况都能关闭打开的文件对象。
【操作】结合异常机制finally确保关闭文件对象
try:
f = open(r"my01.txt","a")
str = "gaoqi"
f.write(str)
except BaseException as e:
print(e)
finally:
f.close()
with关键字(上下文管理器)可以自动管理上下文资源,不论什么原因跳出with块,都能确保文件正确的关闭,并且可以在代码块执行完毕后自动还原进入该代码块时的现场
【示例】使用with管理文件写入操作
s = {'高崎\n','高老二\n','高老四\n'}
with open(r"d.txt","w") as f:
f.write('gaoqi,i love u')
结果:
文件的读取一般使用如下三个方法:(python中文本文件以字符为单位来处理,英文算一个字符,中文也算一个字符)
1.read([size])
从文件中读取size个字符,并作为结果返回。如果没有size参数,则读取整个文件。读取到文件末尾,会返回空字符串
2.readline()
读取一行内容作为结果返回。读取到文件末尾,会返回空字符串
3.readlines()
文本文件中,每一行作为一个字符串存入列表中,返回该列表
【测试】文件较小,一次将文件内容读入到程序中和读取一个文件前3个字符
#测试文件读取
with open(r"e.txt",'r',encoding='utf-8') as f: # 'r'表示读
print(f.read())
print(f.read(3))
结果:
我love u!
百战程序员
上学堂
我lo
【操作】按行读取一个文件
#按行读取一个文件
with open(r"e.txt",'r') as f:
while True:
fragment = f.readline() #读一行
if not fragment: #如果到了末尾
break
else:
print(fragment,end='')
结果:
我love u!
百战程序员
上学堂
【操作】使用迭代器(每次返回一行)读取文本文件
with open(r"e.txt",'r',encoding='utf-8') as f:
for a in f:
print(a,end='')
结果:
我love u!
百战程序员
上学堂
【操作】给每一行加行号
a = ['我love u!\n','尚学堂\n','百战程序员\n']
b = enumerate(a) #把序列传进来,把每一个元素用元组排序
print(a)
print(list(b))
c = [ temp.rstrip()+" #" +str(index) for index,temp in enumerate(a)]
print(c)
结果:
['我love u!\n', '尚学堂\n', '百战程序员\n']
[(0, '我love u!\n'), (1, '尚学堂\n'), (2, '百战程序员\n')]
['我love u! #0', '尚学堂 #1', '百战程序员 #2']
【操作】写入文件
with open('e.txt','r',encoding='utf-8') as f:
lines = f.readlines()
print(lines)
lines = [ line.rstrip()+"#"+str(index) for index,line in enumerate(lines)]
#推导式生成列表
with open('e.txt','w',encoding='utf-8') as f:
f.writelines(lines)
结果:
['我#0l#1o#2v#3e#4#5u#6!#7##80#9百#10战#11程#12序#13员#14##151#16上#17学#18堂#19##202#21']
二进制文件的处理流程和文本文件流程一致。首先还是要创建文件对象,不过,我们需要指定二进制模式,从而创建出二进制文件对象。例如:
f=open(r”d:\a.txt”,’wb’) #可写的、重写模式的二进制文件对象
f=open(r”d:\a.txt”,’ab’) #可写的、追加模式的二进制文件对象
f=open(r”d:\a.txt”,’rb’) #可读的二进制文件对象
创建好二进制文件对象后,仍然可以使用write()、read()实现文件的读写操作
【操作】 读取图片文件,实现文件的拷贝
with open('img.png','rb') as f:
with open('img_copy.png','wb') as w:
for line in f.readlines():
w.write(line)
print('图片拷贝完成')
结果:
文件对象封装了文件相关的操作。在前面我们学习了通过文件对象对文件进行读写操作。本节我们详细列出文件对象的常用属性和方法,并进行说明
文件对象的属性
属性 |
说明 |
name |
返回文件的名字 |
mode |
返回文件的打开模式(r,a,w) |
closed |
若文件关闭则返回True |
文件对象的打开模式
模式 |
说明 |
r |
读模式 |
w |
写模式 |
a |
追加模式 |
b |
二进制模式(可与其他模式组合) |
+ |
读写模式(可以其他模式组合) |
文件对象的常用方法
方法名 |
说明 |
read([size]) |
从文件中读取size个字节或字符的内容返回。若省略[size],则读取到文件末尾,即一次读取文件所有内容 |
readline() |
把文本文件中读取一行内容 |
readlines() |
把文本文件中每一行都作为独立的字符串对象,并将这些对象放入列表返回 |
write(str) |
将字符串str内容写入文件 |
writelines(s) |
将字符串列表s写入文件,不添加换行符 |
seek(offset [,whence]) |
把文件指针移动到新的位置,offset表示相对于whence的多少个字节的偏移量; offset: off为正往结束方向移动,为负往开始方向移动 whence不同的值代表不同含义: 0:从文件头开始计算(默认值) 1:从当前位置开始计算 2:从文件尾开始计算 |
tell() |
返回文件指针的当前位置 |
truncate([size]) |
不论指针在什么位置,只留下指针前size个字节的内容,其余全部删除; 如果没有传入size,则当指针当前位置到文件末尾内容全部删除 |
flush() |
把缓冲区的内容写入文件,但不关闭文件 |
close() |
把缓冲区的内容写入文件,同时关闭文件,释放文件对象相关资源 |
【测试】
with open('ee.txt','r',encoding='utf-8') as f:
print('文件名是:{0}'.format(f.name))
print(f.tell()) #指针的位置
print('读取的内容:{0}'.format(f.readline()))
print(f.tell())
f.seek(3)
print('读取的内容:{0}'.format((f.readline())))
(读取了第一行我’love u! #1’,文件开头是0,‘我’占3个字节,‘love’占4个字节,‘ ’空格占一个字节,‘u’占一个字节,‘!’占一个字节,‘ ’空格占一个字节,‘#1’占两个字节,换行符占一个字节,因此读完第一行后,指针位置在15,即‘尚’前面)(f.seek(3)即把指针移动到偏移量3,然后从头开始读,因此没有读取出‘我’)
结果:
文件名是:ee.txt
0
读取的内容:我love u ! #1
15
读取的内容:love u! #1
python中,一切皆对象,对象本质上就是一个“存储数据的内存块”。有时候,我们需要将“内存块的数据”保存到硬盘上,或者通过网络传输到其他的计算机上。这时候,就需要“对象的序列化和反序列化”。对象的序列化机制广泛的应用在分布式、并行系统上
序列化指的是:将对象转化成“串行化”数据形式,存储到硬盘或通过网络传输到其他地方。反序列化是指相反的过程,将读取到的“串行化数据”转化成对象。
我们可以使用pickle模块中的函数,实现序列化和反序列化
序列化我们使用:
pickle.dump(obj, file) obj就是要被序列化的对象,file指的是存储的文件
pickle.load(file) 从file读取数据,反序列化成对象
【操作】将对象序列化到文件中
import pickle
with open(r'd:\a.dat','wb') as f: #dat是二进制文件
a1 = '高崎'
a2 = 234
a3 = [20,30,40]
pickle.dump(a1,f) #把a1对象存到f文件
pickle.dump(a2,f)
pickle.dump(a3,f)
【操作】将获得的数据反序列化成对象
import pickle
with open(r'd:\a.dat','rb') as f:
a1 = pickle.load(f)
a2 = pickle.load(f)
a3 = pickle.load(f)
print(a1)
print(a2)
print(a3)
print(id(a1));print(id(a2))
执行结果:
高崎
234
[20, 30, 40]
2523987522256
140714953588400
CSV文件的操作
csv(Comma Separated Values)是逗号分隔符文本格式,常用于数据交换、Excel文件和数据库数据的导入和导出。与Excel文件不同,CSV文件中
·值没有类型,所有值都是字符串(只能处理简单的字符串)
·不能指定字体颜色等样式
·不能指定单元格的宽高,不能合并单元格
·没有多个工作表
·不能嵌入图像图表
python标准库的模块csv提供了读取和写入csv格式文件的对象。
我们在excel中建立一个简单的表格:
另存为“csv(逗号分割)”,我们打开查看这个csv文件的内容:
【操作】csv文件的读取和导入
#测试csv文件的读取和导入
import csv
with open("a.csv",'r') as f:
a_csv = csv.reader(f)
#print(list(a_csv))
for row in a_csv:
print(row)
with open('ee.csv','w') as f:
b_csv = csv.writer(f)
b_csv.writerow(['ID','姓名','年龄'])
b_csv.writerow(['100','张三疯','50'])
c=[['101','李四','30'],['102','王五','22']]
b_csv.writerows(c)
结果:
['ID', '姓名', '年龄', '薪资']
['1001', '高崎', '18', '50000']
['1002', '高八', '19', '30000']
['1003', '高九', '20', '20000']
os模块可以帮助我们直接对操作系统进行操作。我们可以直接调用操作系统的可执行文件、命令、直接操作文件、目录等等。是系统运维的核心基础
·os.system可以帮助我们直接调用系统的命令
【示例】os.system调用windows系统的记事本程序,cmd
import os
os.system('notepad.exe')
os.system('cmd')
结果:
【示例】ping www.baidu.com
import os
os.system('ping www.baidu.com')
结果:
【示例】直接调用可执行文件(微信)
import os
os.startfile(r"D:\Program Files (x86)\Tencent\WeChat\WeChat.exe")
os模块和目录操作
我们可以通过前面讲的文件对象实现对于文件内容的读写操作。如果,还需要对文件和目录做其他操作,可以使用os和os.path模块
os模块下常用操作文件的方法
方法名 |
描述 |
remove(path) |
删除指定的文件 |
rename(src,dest) |
重命名文件或目录 |
stat(path) |
返回文件的所有属性 |
listdir(path) |
返回path目录下的文件和目录列表 |
os模块下关于目录操作的相关方法,汇总如下:
方法名 |
描述 |
mkdir(path) #makedir |
创建目录 |
makedirs(path1/path2/path3/...) |
创建多级目录 |
rmdir(path) #removedir |
删除目录 |
removedirs(path1/path2..) |
删除多级目录(只能删除空的目录) |
getcwd() |
返回当前工作目录:current work dir |
chdir(path) #changedir |
把path设为当前工作目录 |
walk() |
遍历目录树 |
sep |
当前操作系统所使用的路径分隔符 |
【示例】os模块:创建、删除目录、获取文件信息等
【文件和文件夹相关的信息】
#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() 函数将对象转化为供解释器读取的形式。返回一个对象的 string 格式。
print(os.stat('02.py'))
结果:
nt
\
'\r\n'
os.stat_result(st_mode=33206, st_ino=5348024557647051, st_dev=2620880335, st_nlink=1, st_uid=0, st_gid=0, st_size=1161, st_atime=1641783240, st_mtime=1641783240, st_ctime=1641781008)
【关于工作目录的操作】
print(os.getcwd()) #当前的工作空间
#os.chdir('d:') #改变d盘设为工作目录
#os.mkdir('书籍') #创建子目录,书籍建在d盘
结果:
C:\Users\Administrator\PycharmProjects\MyDemo\test_os
【创建目录、创建多级目录、删除】
#os.mkdir('书籍')
#os.rmdir('书籍') #相对路径都是相对于当前的工作目录
#os.makedirs('电影/港台/周星驰') #创建多级目录
#os.removedirs('电影/港台/周星驰') #删除多级目录(只删除空目录)
#os.makedirs('../音乐/香港/刘德华') #../指的是上一级目录
#os.rename('电影','movie') #把目录名电影改为movie
dirs = os.listdir('movie') #查看子目录
print(dirs)
结果:
['大陆', '港台']
os.path模块
os.path模块提供了目录相关(路径判断、路径切分、路径连接、文件夹遍历)的操作
方法 |
描述 |
isabs(path) |
判断path是否绝对路径 |
isdir(path) |
判断path是否为目录 |
isfile(path) |
判断path是否为文件 |
exists(path) |
判断指定路径的文件是否存在 |
getsize(filename) |
返回文件的大小 |
abspath(path) |
返回绝对路径 |
dirname(p) |
返回目录的路径 |
getatime(filename) |
返回文件的最后访问时间 |
getmtime(filename) |
返回文件的最后修改时间 |
getctime(filename) |
返回文件的创建时间 |
walk(top,func,arg) |
递归方式遍历目录 |
join(path,*paths) |
连接多个path |
split(path) |
对路径进行分割,以列表形式返回 |
splitext(path) |
从路径中分割文件的扩展名 |
【操作】测试os.path中关于目录、路径的操作
【判断:绝对路径、是否目录、是否文件、文件是否存在】
import os
import os.path
##判断:绝对路径、是否目录、是否文件、文件是否存在######
print(os.path.isabs('d:/a.txt')) #True
print(os.path.isdir('d:/a.txt')) #False
print(os.path.isfile('d:/a.txt')) #True
print(os.path.exists('d:/a.txt')) #True
结果:
True
False
True
True
【获得文件基本信息】
print(os.path.getsize('b.txt'))
print(os.path.abspath('b.txt'))
print(os.path.dirname('b,txt'))
print(os.path.getctime('b.txt')) #创建时间
print(os.path.getatime('b.txt'))
print(os.path.getmtime('b.txt'))
结果:
16
C:\Users\Administrator\PycharmProjects\MyDemo\test_os\b.txt
1641786490.3599086
1641786541.197391
1641786541.197391
【对路径的操作】
path = os.path.abspath('b.txt')
print(os.path.split(path))
print(os.path.splitext(path))
print(os.path.join('aa','bb','cc'))
结果:
('C:\\Users\\Administrator\\PycharmProjects\\MyDemo\\test_os', 'b.txt')
('C:\\Users\\Administrator\\PycharmProjects\\MyDemo\\test_os\\b', '.txt')
aa\bb\cc
【练习】列出指定目录下所有的.py文件,并输出文件名
import os
path = os.getcwd()
file_list = os.listdir(path) #列出子目录、子文件
for filename in file_list:
if filename.endswith('py'):
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')
结果:
01.py 02.py 03.py 04.py ####列表推导式####
01.py 02.py 03.py 04.py
os.walk()方法:
返回一个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)
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)
结果:
shutil模块是python标准库中提供的,主要用来做文件和文件夹的拷贝、移动、删除等;还可以做文件和文件夹的压缩、解压缩操作。
os模块提供了对目录或文件的一般操作。shutil模块作为补充,提供了移动、复制、压缩、解压等操作,这些os模块都没有提供
【操作】测试shutil模块的用法,拷贝
#coding=utf-8
#测试shutil模块的用法,拷贝、压缩
import shutil
#文件拷贝
#shutil.copyfile('1.txt','1_copy.txt')
#文件夹拷贝(’电影‘目录不存在时才能正常拷贝)
#ignore指不拷贝的内容(此处不拷贝.txt和.html)
shutil.copytree('movie/港台','电影',ignore=shutil.ignore_patterns('*.txt','*.html'))
结果:
【操作】文件压缩
import shutil
#####压缩、解压缩####
shutil.make_archive('电影/gg','zip','movie/港台')
#(压缩完之后压缩包所在的位置和名字,压缩包的格式,压缩的内容)
结果:
【操作】压缩文件,解压缩
import zipfile
z1 = zipfile.ZipFile('d:/a.zip','w')
z1.write('1.txt')
z1.write('1_copy.txt')
z1.close()
#解压缩
x2 = zipfile.ZipFile('d:/a.zip','r')
x2.extractall('电影')
x2.close()
结果:
递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者简介的调用自己。
利用递归可以用简单的程序来解决一些复杂的问。比如:斐波那契数列的计算、汉诺塔、快排等问题
递归结构包括两个部分:
·定义递归头。解答:什么时候不调用自身方法。如果没有头、将陷入死循环,也就是递归的结束条件。
·递归体。解答:什么时候需要调用自身方法。
【操作】测试递归算法
#coding = utf-8
#测试递归算法
num = 1
def a1():
global num #如果要在函数内部改变全局变量的值,增加global关键字声明一下
num +=1
print('a1')
if num<3:
a1()
a1()
结果:
a1
a1
【示例】使用递归求n!
#coding = utf-8
#使用递归计算n的阶乘
def factorial(n):
if n == 1:
return n
else:
return n*factorial(n-1)
print(factorial(5))
结果:120
【操作】递归打印所有的目录和文件
#coding=utf-8
#递归打印所有的目录和文件
import os
allfiles = []
def getAllFiles(path,level):
childFiles = os.listdir(path)
for file in childFiles:
filepath = os.path.join(path,file)
if os.path.isdir(filepath):
getAllFiles(filepath,level-1) #如果是目录的话
#print('\t'*level+filepath)
allfiles.append('\t'*level+filepath)
getAllFiles(r"C:\Users\Administrator\PycharmProjects\MyDemo\递归",0)
for f in reversed(allfiles):
print(f)
结果:
E:\Softwares\Anaconda3\python.exe C:/Users/Administrator/PycharmProjects/MyDemo/递归/递归打印所有目录文件.py
C:\Users\Administrator\PycharmProjects\MyDemo\递归\递归打印所有目录文件.py
C:\Users\Administrator\PycharmProjects\MyDemo\递归\递归.py
C:\Users\Administrator\PycharmProjects\MyDemo\递归\n!.py