我们第一节学习了文件 IO(我们所说的文件 IO 特指磁盘 IO),我们所指的文件是存储在磁盘上的文件。
大家还记的文件对象的定义吗,我们知道像 open 函数返回的这种有个 read 函数的对象,在 Python 中统称为文件对象。文件对象不一定是必须要和磁盘,网卡这种外部设备打交道,和内存打交道的具有 read 和 write 函数的对象,我们也叫文件对象。
对磁盘的读写操作速度没有直接对内存操作快,很多时候,数据读写不一定是文件,也可以在内存中读写,本节我们就来学习对内存操作的文件对象 StringIo 和 BytesIO。
要把字符串写入 StringIO,我们需要先创建一个 StringIO 对象,然后调用 StringIO 对象的 write 函数写入字符串(字符串必须为 unicode 类型),然后我们可以通过 StringIO 对象的 getvalue 函数读取字符串,注意使用完后不要忘记调用文件对象的 close 函数。
from io import StringIO f = StringIO() f.write("hello") print(f.getvalue()) f.close() # 不要忘记调用文件对象的 close 函数
我们也可以在创建 StringIO 对象的时候写入字符串。
from io import StringIO f = StringIO(u"hello") # 调用 StringIO 的构造函数写入字符串 print(f.getvalue()) f.close()
我们也可以使用文件对象的 read 函数读取字符串,但要注意调用 write 函数 和 read 函数会导致把指针指向字符串的末尾。
from io import StringIO f = StringIO() f.write("hello") print(f.read()) # 从字符串末尾开始读,所以没有内容 f.close() # 不要忘记调用文件对象的 close 函数
我们可以使用 seek 函数把指针定位到开始,然后调用 read 函数读取,或者直接使用 getvalue 函数读取(总是从字符串开始位置读取)。
from io import StringIO f = StringIO() 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。
class myStringIO(): def __init__(self, data): self.data = data def read(self): return self.data def write(self, data): self.data = data def getvalue(self): return self.data def close(self): del self.data f = myStringIO(u"hello") print(f.getvalue()) f.close()
StringIO 操作的只能是字符串,如果要操作二进制数据(视频,图片,音频等等非字符流数据),就需要使用 BytesIO,下面我们使用 BytesIO 进行读写图片。注意,BytesIO 接收的参数和返回的结果都是字节类型。
from io import BytesIO fother = open("d:/test.png", "rb") # 确保 d 盘下有 test.png 文件 data = fother.read() fother.close() f = BytesIO(data) # data 是字节类型 print(f.getvalue()) # 结果为字节类型 f.close()
为了弄明白 BytesIO 使用的场景,我们从一个例子说起。我们写个程序:从网络上下载二进制数据(视频,图片,音频等等非字符流数据),然后存储在磁盘中。
from io import BytesIO from urllib import request # 网络库 rst = request.urlopen("http://birdpython.com/static/img/logo.png") # 下载图片 f = BytesIO(rst.read()) # 把图片二进制文件放入 BytesIO 对象中 fdisk = open("d:/xx.png", "wb") fdisk.write(f.getvalue()) fdisk.close() f.close()
同学们可能会想,我们也可以直接把网上下载的二进制数据存到磁盘中啊,为什么还要用 BytesIO 对象存储起来,这不是多此一举吗!如下代码。
from urllib import request # 网络库 import ssl context = ssl._create_unverified_context() rst = request.urlopen("http://birdpython.com/static/img/logo.png", context=context) # 下载图片 data = rst.read() fdisk = open("d:/xx.png", "wb") fdisk.write(data) fdisk.close()
当然,如果只是这种简单的需求,我们完全不需要使用 BytesIO,那么,现在我们增加一项需求,要求把下载的图片大小处理成 64 像素高和 64 像素宽,然后再存储起来。这个时候,我们可以使用专业的图片处理模块 PIL 提供的函数来修改图片大小,然而 PIL 模块需要传入一个文件对象或者类文件对象(不能直接传入字节类型变量)来操作,这时候我们可以传入一个存储该二进制图片数据的 BytesIO 对象给 PIL 对象。
from io import BytesIO from urllib import request # 网络库 from PIL import Image # 第三方图形处理模块(安装命令:pip install pillow) import ssl context = ssl._create_unverified_context() rst = request.urlopen("http://birdpython.com/static/img/logo.png", context=context) # 下载图片 data = rst.read() f = BytesIO(data) # 把图片二进制文件放入 BytesIO 对象中 img = Image.open(f) # 需要传入一个文件对象 newimg = img.resize((64, 64)) # 修改图片像素大小为 64 x 64 newimg.save("d:/xx.png") # 存储处理后的图片 f.close()
如果你是个杠精,你说你可以不用这些专业的图形处理模块来完成需求,比如自己去写业务逻辑来改变图片大小,那如果有更复杂的图片处理需求呢?我可以告诉你,这几乎是不可能的事情!
内存 IO 和文件 IO 的本质区别就在于文件 IO 是程序通过操作系统操作磁盘文件,而内存 IO 是我们直接用程序操作内存。
如果我们想要存储的内容持久化就只能使用文件 IO,因为内存 IO 是对内存进行操作,在我们进程结束后,内存就被操作系统回收了。
文件 IO 有读写标识符,内存 IO 没有读写标识符,大家想想这是不是理所当然的。
会使用 StringIO 文件对象。
会使用 BytesIO 文件对象。
知道 StringIO 和 BytesIO 的区别。
从网上下载图片二进制数据存储在 BytesIO 对象里面,然后使用 PIL 模块( PIL 是第三方模块,需要先使用 pip install pillow 命令安装该模块)调整图片高宽大小为 200 * 200 像素,然后存储在磁盘上。
为什么都这么快 你们有其他语言基础吗