ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Python3基础】15-文件、目录与内存流

【Python3基础】15-文件、目录与内存流 ▒ 目录 ▒ 导读本篇知识路线1️⃣ 文件IO读文件写文件文件对象2️⃣ 操作文件和目录windows 和 linux 命令行操作文件和目录Python 操作文件的函数Python 操作目录的函数Python 操作文件名和目录名的函数使用高级模块 shutil 操作文件和路径3️⃣ StringIO和BytesIOStringIOBytesIO内存 IOStringIO 和 BytesIO和文件 IO 的主要区别4️⃣ 路径和编码要在边界处明确 文章小结 导读文件操作的关键不只是读写成功还包括编码正确、资源及时关闭和路径能够跨平台工作。本篇覆盖文件打开、读取、写入、文件指针、目录操作以及StringIO、BytesIO。原文保留了大量直接示例学习时要同时关注文本与二进制的区别并养成使用上下文管理器自动释放资源的习惯。本篇知识路线外部文件 ──► open() ──► 文件对象 ──► read()/write() │ └─ with 代码块结束后自动关闭 内存文本 ──► StringIO 内存字节 ──► BytesIO1️⃣ 文件IO大家回顾一下我们前面所学我们程序中需要存储数据都是存储在内存中然而当进程运行中的程序结束的时候 我们的内存就会被操作系统所回收我们的数据也就没了。如果我们能把数据存储在磁盘中别说是进程结束哪怕是关机我们的数据都不会丢失。操作系统不允许我们自己写的普通的程序直接操作磁盘在磁盘上读写文件的功能都是由操作系统提供的 所以读写文件就是请求操作系统打开一个文件对象通常称为文件描述符 然后通过操作系统提供的接口从这个文件对象中读取数据读文件或者把数据写入这个文件对象写文件。这种在程序中通过文件对象对磁盘进行读写的操作我们叫做磁盘 IO当然磁盘 IO 属于文件 IO。在本节课的讲解中我们统一称磁盘 IO 为文件 IO大家都习惯于这样称呼我们知道 IO 是指输入和输出也就是读写文件 IO 我们也称之为文件读写大家要知道这种叫法。读文件读文件是最常见的 IO 操作Python 内置了读文件的函数比如要以读文件的模式打开一个文件对象使用 Python 内置的 open 函数传入文件名和标识符其中标识符 “r” 表示读文本文件“rb” 表示读二进制文件图片音乐视频等。但是如果读取的文件不存在open 函数就会抛出一个 IOError 的错误并且给出错误码和详细的信息告诉你文件不存在。最后要注意文件使用完毕后必须关闭因为文件对象会占用操作系统的资源并且操作系统同一时间能打开的文件数量也是有限的调用 close 方法关闭文件。fopen(d:/test.txt,r)# 要确保 d 盘下存在 test.txt 文件否则会报异常dataf.read()print(data)f.close()# 用完后关闭文件对象在程序中我们成功打开一个文件进行操作并且最后我们没有调用 close 函数关闭文件对象进程结束操作系统会自动回收该进程所申请的所有资源文件对象也是资源。 但是最好不要这样去做我们要养成一个好的习惯因为我们的程序很有可能循环调用多次这个 open 函数或者我们是一个提供服务功能的程序是需要长期一直执行的。fopen(d:/test.txt,r)# 要确保 d 盘下存在 test.txt 文件dataf.read()print(data)# 进程结束操作系统会回收改进程申请的所有资源文件对象我们成功打开一个文件进行操作时由于后面的语句错误可能导致程序异常这样的话程序就不会执行到我们调用文件对象的 close 函数。所以为了保证无论是否出错都能正确地关闭文件我们可以使用 try … except 来实现。try:fopen(d:/test.txt,r)dataf.read()print(data)print(data.fuck)# 报异常退出 try 语句进入 except 语句f.close()# 解释器不会执行这条语句except:f.close()raise(文件操作出现错误)每次操作文件都要写这种异常处理语句感觉很繁琐这完全不符合 Python 高大上的形象 我们可以使用 Python 引入的 with 语句来自动帮我们自动调用 close 函数。withopen(d:/test.txt,r)asf:dataf.read()print(data)print(data.fuck)# 虽然报异常但在 with 块内解释器会自动调用 close 函数关闭文件对象当然在 with 语句内不报异常解释器在离开 with 语句块的时候也会调用 close 函数关闭文件对象。withopen(d:/test.txt,r)asf:dataf.read()print(data)print(f.read())# 报异常因为解释器在离开 with 语句块的时候会调用 close 函数关闭文件对象。我们的 open 函数返回的是向操作系统申请的文件资源 该文件资源我们也叫作文件对象或者文件描述符同学们可能会想程序执行过程中出现异常进程就会退出 进程退出操作系统就会回收进程申请的所有资源我们通过 open 函数申请的文件对象就会被操作系统所回收 自然就不用我们调用文件对象的 close 函数了其实如果我们程序只有一个进程倒是无所谓但如果我们的程序有多个进程 且这个出现异常的语句是在子进程中子进程出现异常我们的主进程并不会退出 而且我们主进程经常会调用到这个向操作系统申请文件对象的崩溃的子进程那就会耗干操作系统维护的文件资源。 在后面章节中我们会学习到多进程编程。调用 read 函数会一次性读取文件的全部内容如果我们读取的文件很大就有可能出现内存不够用而失败 所以要保险起见可以反复调用 read(size) 方法每次最多读取 size 个字节的内容。 注意不要用记事本写入文件内容最好使用 notepad保存为 utf8 格式。withopen(d:/test.txt,r)asf:whileTrue:dataf.read(5)ifdata:# 没有数据了退出循环breakprint(data)调用 readline 函数可以每次读取一行内容。withopen(d:/test.txt,r)asf:whileTrue:datalinef.readline()ifdataline:# 没有数据了退出循环breakprint(dataline)调用 readlines 函数一次读取所有内容并按行返回 list。withopen(d:/test.txt,r)asf:datalinesf.readlines()print(datalines)我们知道用 “rb” 的方式表示读二进制文件图片音乐视频等但要注意在 Python3 中读取的内容是 bytes 类型。fopen(d:/test.png,rb)# 要确保 d 盘下存在 test.png 文件否则会报异常dataf.read()print(type(data))# class bytesf.close()# 用完后关闭文件对象最后要注意我们打开文件一定要掉用 close 函数关闭文件对象否则进程会占用该文件对象的句柄导致对文件本身的操作无效比如删除该文件大家可以做个测试。写文件写文件和读文件打开使用的函数是一样的唯一区别是调用 open 函数时传入标识符 “w” 或者 “wb”其中标识符 “w” 表示写文本文件“wb” 表示写二进制文件。f open(d:/test.txt, w) f.write(birdpython) f.close() # 调用 close 函数关闭文件对象我们可以反复调用 write 函数来写入文件文件对象有一个指针会指向每一次写入内容的最后一个位置 这样下一次要写入的内容会紧跟着上一次写入内容的末尾当然我们也可以修改这个指针的指向。f open(d:/test.txt, w) f.write(birdpython) f.write(byebye) # 紧跟着 n 后面的位置写入 byebye f.seek(1) f.write(haha) # 会从 i 位置开始往后写如 haha f.close()对文件写操作执行完成后要调用 close 函数来关闭文件当我们写文件时操作系统往往不会立刻把数据写入磁盘而是放到内存缓存起来空闲的时候再慢慢写入。只有调用 close 函数时操作系统才保证把没有写入的数据全部写入磁盘。忘记调用 close 的后果是数据可能只写了一部分到磁盘或者没有写入。f open(d:/test.txt, w) f.write(birdpython) input(waiting...) # 进程阻塞在此处没有调用 close 函数且进程没有退出操作系统并不会把 birdpython 写入磁盘 f.close()但如果我们要间断性的写入很多内容我们想每次执行 write 函数后就把内容写入到磁盘中如果每写一句 write函数就调用一次 close 函数释放文件对象我们下次写的话还要重新申请文件对象这样频繁的向操作系统申请和释放文件对象是很耽搁时间的是一种很 low 的解决方案。 我们可以使用 flush 函数告诉操作系统不需要等待 close 函数可以马上写入内容到磁盘。f open(d:/test.txt, w) f.write(ubirdpython) f.flush() f.write(ubyebye) f.flush() f.write(uhahaha) f.close()我们每次使用标识符 “w” 写文件的方式返回一个文件对象如果打开的文件存在操作系统会删除该文件然后重新写入 这样会丢失已有的文件。f open(d:/test.txt, w) f.write(ubirdpython) f.close() f open(d:/test.txt, w) # 操作系统会删除掉 test.txt 文件然后重新创建 f.write(ubyebye) f.close() # 最后文件里的内容只有 byebye我们可以使用标识符 “a”这样可以追加方式写文件会在原有的文件内容后面写入我们的内容。f open(d:/test.txt, w) # 操作系统会删除掉 test.txt 文件然后重新创建 f.write(ubirdpython) f.close() f open(d:/test.txt, a) # 操作系统会在 test.txt 文件内容最后一个位置追加写 f.write(ubyebye) f.close()读写文件还有标识符 “w”“r”“a” 等等大家用到时候可以自行 google 搜索。注意当我们以写方式通过文件对象打开一个文件在未调用 close 函数之前最好不要做对该文件内容的修改操作比如写文件或文件本身的修改操作比如删除文件。文件对象像 open 函数返回的这种有个 read 函数的对象在 Python 中统称为 file-like Object。除了 file 还可以是内存的字节流网络流自定义流等等。file-like Object 不要求从特定类继承只要写个 read 函数就行。我们后面会学习各种文件对象特别是在网络编程中我们要学习的套接字也是文件对象套接字的 read 函数是从网卡中读入字节流write 函数是告诉操作系统把字节流写入到网卡这样就可以和远程机器进行通信了。文件对象不一定是必须要和磁盘网卡这种外部设备打交道和内存打交道的具有 read 和 write 函数的对象我们也叫文件对象下节我们会学习。2️⃣ 操作文件和目录我们平时在电脑上操作读写删除复制等等文件和目录主流的操作系统带界面的 linux 系统mac 系统windows 系统等等不但都有提供友好的可视化操作也提供命令行操作。无论是可视化操作还是通过命令行操作最终都是转化为程序调用操作系统的 API 函数对文件和目录进行操作。本节课我们来学习 Python 内置的操作文件和目录的函数。windows 和 linux 命令行操作文件和目录如果我们要操作文件或目录可以在命令行下面输入操作系统提供的各种命令来完成比如 linux下的 pwd 显示当前路径 ll 和 ls 显示当前目录下的文件和文件夹rm 删除文件mv 移动文件等等。Windows 下的 dir 命令显示当前目录下的文件和文件夹copy 复制文件move 移动文件del 删除当前路径下的文件等等。对于操作系统提供的操作文件和目录的命令大家用到时可以自行搜索我们下面重点学习如何用 Python 提供的函数来操作文件和目录。Python 操作文件的函数我们可以使用 Python 内置的 os 模块提供的函数来检查一个文件是否存在。importos flagos.path.exists(d:/test.txt)print(flag)# 如果文件存在返回 True否则返回 False我们上节课学习的写文件操作中可以用标识符 ‘w’ 打开一个不存在文件我们可以使用这种方法来创建一个文件 但要注意我们最好在创建文件时检查一下是否存在有重名的文件如果存在就不要创建否则的话会把原来的文件给冲掉。importosifnotos.path.exists(d:/test.txt):# 如果文件不存在fopen(d:/test.txt,w)# 在 d 盘下创建一个 test.txt 文件f.close()else:print(文件已存在请换个文件名)我们可以使用 Python 内置的 os 模块提供的函数来删除一个文件如果删除的文件不存在 Python 解释器会报异常。importosifos.path.exists(d:/test.txt):# 如果文件存在os.remove(d:/test.txt)# 删除 d 盘下的 test.txt 文件else:print(要删除的文件不存在)我们可以使用 Python 内置的 os 模块提供的函数来给一个存在的文件重命名要确保被命名的文件存在以及新命名的文件名不存在否则Python 解释器会报异常。importosifos.path.exists(d:/test.txt)andnotos.path.exists(d:/newtest.txt):os.rename(d:/test.txt,d:/newtest.txt)else:print(不存在该文件或重命名的文件名已存在)我们还需要对文件进行拷贝移动等等操作后面我们会介绍更高级的模块来完成这些操作。Python 操作目录的函数我们可以使用 Python 内置的 os 模块提供的函数查看当前文件所在目录的全路径。importosprint(os.path.abspath(.))我们可以使用 Python 内置的 os 模块提供的函数在已经存在的目录下创建一个新目录。importos os.mkdir(d:/test/tt)# 要确保 d:/test/ 目录存在否则会报异常我们可以使用 Python 内置的 os 模块提供的函数删除一个存在的空目录该目录下没有文件和目录。importos os.rmdir(d:/test/tt)# 要确保 d:/test/tt 目录下没有文件和目录否则会报异常如何删除一个存在的非空目录以及更复杂的目录操作后面我们会介绍更高级的模块来完成这些操作。Python 操作文件名和目录名的函数把目录和文件进行路径拼接时如果直接拼字符串我们还要判断操作系统因为在 Linux/Unix/Mac 下os.path.join 函数返回的字符串是用 “/” 衔接的在 windows 下os.path.join 函数返回的字符串是用 “” 衔接的。我们可以使用 Python 内置的 os 模块的函数进行智能拼接路径。importos filepathd:/img# 我们在项目开发中一般不会使用绝对路径在此只是为了举例说明filenameruhua.png 注意 1.filepath 中的字符串中的目录不要求在系统中存在我们拼接的 filepath 和 filename 仅仅只是字符串拼接而已 2.我们 定义的 filepath 仅仅是在 windows 下做测试实际项目开发中我们不会用这种绝对路径的方式因为我们 的项目很有可能部署在不同的操作系统上那样的话我们还要修改连接符 \\ 或者 / 来对应我们的操作系统这 显然不符合软件工程的理念。 3.所以下面这条语句在 windows 下会拼接成 d:/img\ruhua.png在 linux 下会拼接成 d:/img/ruhua.png 我们想要的拼接是字符串 d:/test 和 字符串 ruhua.png 的拼接所以实现了智能拼接。 print(os.path.join(filepath,filename))我们要拆分一个已存在文件或目录的全路径时也不要直接去拆字符串我们可以使用 Python 内置的 os 模块的函数这样可以把一个路径拆分为两部分放在一个 tuple 里面tuple 中的第二个值总是最后级别的目录或文件名。importos allfilepathd:/img/head/ruhua.png# 不要求系统中存在该路径拆分的仅仅是字符串print(os.path.split(allfilepath))# 返回值为 (d:/img/head, ruhua.png)我们可以使用 Python 内置的 os 模块的函数得到文件扩展名同样我们操作的对象是字符串并不要求这个路径存在。importos allfilepathd:/img/head/ruhua.png# 不要求系统中存在该路径拆分的仅仅是字符串print(os.path.splitext(allfilepath))# 返回值为 (d:/img/head/ruhua, .png)使用高级模块 shutil 操作文件和路径Python 内置的 os 模块只提供基本的文件操作对于一些复杂的操作比如对文件的拷贝操作 删除非空文件夹等等我们可以我们可以使用高级模块比如 shutil 模块。importshutil shutil.copy(d:/test.txt,d:/testcp.txt)# 拷贝文件shutil.rmtree(d:/testdir)# testdir 是非空文件夹当然即使我们不使用 shutil 模块使用 Python 提供的 os 模块也可以实现拷贝文件删除非空文件夹等等所有的对文件的操作其实 shutil 模块里面也是调用 os 模块来完成这些复杂的文件操作。3️⃣ StringIO和BytesIO我们第一节学习了文件 IO我们所说的文件 IO 特指磁盘 IO我们所指的文件是存储在磁盘上的文件。大家还记的文件对象的定义吗我们知道像 open 函数返回的这种有个 read 函数的对象在 Python 中统称为文件对象。文件对象不一定是必须要和磁盘网卡这种外部设备打交道和内存打交道的具有 read 和 write 函数的对象我们也叫文件对象。对磁盘的读写操作速度没有直接对内存操作快很多时候数据读写不一定是文件也可以在内存中读写本节我们就来学习对内存操作的文件对象 StringIo 和 BytesIO。StringIO要把字符串写入 StringIO我们需要先创建一个 StringIO 对象然后调用 StringIO 对象的 write 函数写入字符串字符串必须为 unicode 类型然后我们可以通过 StringIO 对象的 getvalue 函数读取字符串注意使用完后不要忘记调用文件对象的 close 函数。fromioimportStringIO fStringIO()f.write(hello)print(f.getvalue())f.close()# 不要忘记调用文件对象的 close 函数我们也可以在创建 StringIO 对象的时候写入字符串。fromioimportStringIO fStringIO(uhello)# 调用 StringIO 的构造函数写入字符串print(f.getvalue())f.close()我们也可以使用文件对象的 read 函数读取字符串但要注意调用 write 函数 和 read 函数会导致把指针指向字符串的末尾。fromioimportStringIO fStringIO()f.write(hello)print(f.read())# 从字符串末尾开始读所以没有内容f.close()# 不要忘记调用文件对象的 close 函数我们可以使用 seek 函数把指针定位到开始然后调用 read 函数读取或者直接使用 getvalue 函数读取总是从字符串开始位置读取。fromioimportStringIO fStringIO()f.write(hello)f.seek(0)# 指针重新定位到字符串开始print(f.read())# 读完后指针又定位到字符串末尾f.seek(0)# 指针重新定位到字符串开始print(f.read())# 读完后指针又定位到字符串末尾print(f.getvalue())# getvalue 函数总是从字符串开始位置开始读f.close()# 不要忘记调用文件对象的 close 函数用 StringIO 模块进行读写操作和我们自己定义对象读写字符串实质上是一样的只是 StringIO 模块里面有类似文件对象提供的函数更方便于我们对字符串进行操作同理我们可以自己定义一个类来实现一个简易型的 myStringIO。classmyStringIO():def__init__(self,data):self.datadatadefread(self):returnself.datadefwrite(self,data):self.datadatadefgetvalue(self):returnself.datadefclose(self):delself.data fmyStringIO(uhello)print(f.getvalue())f.close()BytesIOStringIO 操作的只能是字符串如果要操作二进制数据视频图片音频等等非字符流数据就需要使用 BytesIO下面我们使用 BytesIO 进行读写图片。注意BytesIO 接收的参数和返回的结果都是字节类型。fromioimportBytesIO fotheropen(d:/test.png,rb)# 确保 d 盘下有 test.png 文件datafother.read()fother.close()fBytesIO(data)# data 是字节类型print(f.getvalue())# 结果为字节类型f.close()为了弄明白 BytesIO 使用的场景我们从一个例子说起。我们写个程序从网络上下载二进制数据视频图片音频等等非字符流数据然后存储在磁盘中。fromioimportBytesIOfromurllibimportrequest# 网络库rstrequest.urlopen(http://birdpython.com/static/img/logo.png)# 下载图片fBytesIO(rst.read())# 把图片二进制文件放入 BytesIO 对象中fdiskopen(d:/xx.png,wb)fdisk.write(f.getvalue())fdisk.close()f.close()同学们可能会想我们也可以直接把网上下载的二进制数据存到磁盘中啊为什么还要用 BytesIO 对象存储起来这不是多此一举吗如下代码。fromurllibimportrequest# 网络库importssl contextssl._create_unverified_context()rstrequest.urlopen(http://birdpython.com/static/img/logo.png,contextcontext)# 下载图片datarst.read()fdiskopen(d:/xx.png,wb)fdisk.write(data)fdisk.close()当然如果只是这种简单的需求我们完全不需要使用 BytesIO那么现在我们增加一项需求要求把下载的图片大小处理成 64 像素高和 64 像素宽然后再存储起来。这个时候我们可以使用专业的图片处理模块 PIL 提供的函数来修改图片大小然而 PIL 模块需要传入一个文件对象或者类文件对象不能直接传入字节类型变量来操作这时候我们可以传入一个存储该二进制图片数据的 BytesIO 对象给 PIL 对象。fromioimportBytesIOfromurllibimportrequest# 网络库fromPILimportImage# 第三方图形处理模块安装命令pip install pillowimportssl contextssl._create_unverified_context()rstrequest.urlopen(http://birdpython.com/static/img/logo.png,contextcontext)# 下载图片datarst.read()fBytesIO(data)# 把图片二进制文件放入 BytesIO 对象中imgImage.open(f)# 需要传入一个文件对象newimgimg.resize((64,64))# 修改图片像素大小为 64 x 64newimg.save(d:/xx.png)# 存储处理后的图片f.close()如果你是个杠精你说你可以不用这些专业的图形处理模块来完成需求比如自己去写业务逻辑来改变图片大小那如果有更复杂的图片处理需求呢我可以告诉你这几乎是不可能的事情内存 IOStringIO 和 BytesIO和文件 IO 的主要区别内存 IO 和文件 IO 的本质区别就在于文件 IO 是程序通过操作系统操作磁盘文件而内存 IO 是我们直接用程序操作内存。如果我们想要存储的内容持久化就只能使用文件 IO因为内存 IO 是对内存进行操作在我们进程结束后内存就被操作系统回收了。文件 IO 有读写标识符内存 IO 没有读写标识符大家想想这是不是理所当然的。4️⃣ 路径和编码要在边界处明确文本文件读写时应明确编码路径拼接则应使用路径工具表达目录关系避免手工拼接不同平台的分隔符。写入重要文件时可以先写临时文件确认成功后再替换目标文件减少程序中断造成半截文件的风险。 文章小结区分文本与二进制读写、打开模式和文件指针用with确保文件及时关闭。用pathlib、os或shutil处理路径、目录和文件StringIO存放文本BytesIO存放二进制数据。可列出非空目录中的.py文件并尝试将图片读入BytesIO、调整尺寸后写回磁盘观察内存流与磁盘文件的关系。
返回列表