open函数是一个工厂函数。根据传入的变量,open函数会进行文件的开启、文件对象的创建与设定,然后返回文件对象。我们来看一下指定不同变量时,open函数具体为我们返回了什么类型的文件对象呢:
>>> open('Documents/me.txt')
<_io.TextIOWrapper name='Documents/me.txt' mode='r' encoding='cp936'>
>>> open('Documents/me.txt','rb')
<_io.BufferedReader name='Documents/me.txt'>
>>> open('Documents/me.txt','rb',buffering = 0)
<_io.FileIO name='Documents/me.txt' mode='rb' closefd=True>
Python的IO大致分为三个类型:文本IO(Text IO)、二进制IO(Binary IO)和原始IO(Raw IO)。而上面的TextIOWrapper、BufferedReader、FileIO恰好就是上述三种类型的代表者。
我们通过一张图了解一下io模块中类的继承关系:
接下来我们就可以手动建立文件对象了。
原始IO是无缓冲(Buffer)的低阶字节流操作。如果想以最基本的二进制方式读取文件,就可以直接创建一个FileIO对象。
>>> import io
>>> with io.FileIO('Documents/me.txt') as fin:
print(fin.read())
b'\xd1\xa7\xbb\xe1\xcb\xbc\xbf\xbc\xa3\xac\xb0\xae\xc9\xcf\xb1\xe0\xb3\xcc\xa1\xa3\r\n\r\n\r\n'
化名为fin的FileIO对象会逐字节读取文件的内容。FileIO支持的操作模式包括r\w\x\a, 默认是r。由于是面向字节的,所以不存在区分b还是t这样的额外说明。
为了提升FileIO的效率,可以为其加上缓存:
>>> import io
>>> with io.BufferedReader(io.FileIO('Documents/me.txt')) as fin:
print(fin.read())
b'\xd1\xa7\xbb\xe1\xcb\xbc\xbf\xbc\xa3\xac\xb0\xae\xc9\xcf\xb1\xe0\xb3\xcc\xa1\xa3\r\n\r\n\r\n'
BufferedReader以及BufferedWriter的作用就是一个包裹器,包裹RawIOBase实例。典型的装饰者模式,提升文件的读取效率。
TextIOWrapper也是一个包裹器,包裹BufferedIOBase实例,在缓存式读取二进制内容的基础上,通过指定的字符集,在二进制与字符之间进行转化:
>>> import io
>>> with io.TextIOWrapper(io.BufferedReader(io.FileIO('Documents/me.txt')),'utf8') as fin:
print(fin.read())
学会思考,爱上编程。
如果数据的读取来源或写入目的地并不是磁盘文件,而是内存中的某个对象,那么可以使用BytesIO和StringIO,与文件对象相似的API在内存中对数据进行操作,效率会大大提升。
BytesIO是BufferedIOBase的子类,可以直接构造实例(空的),或者指定一个初始的bytes类型对象,基于该对象进行实例创建。有了BytesIO实例,就可以进行数据的写入和读取了:
import io
bio = io.BytesIO(b'hello, python')
print(bio.read())
bio.seek(6)
bio.write(b'Kitty:)')
print(bio.getvalue())
运行结果:
b'hello, python'
b'hello,Kitty:)'
在对BytesIO实例操作完毕后,通常使用getvalue函数获取全部数据。
BytesIO是基于字节,StringIO就是基于字符的。StringIO是TextIOBase的子类,与BytesIO类似,对StringIO的操作完毕后,也使用getvalue函数获取全部数据。
import io
sio = io.StringIO()
sio.write('hello, python')
sio.write(' and ')
sio.write('Kitty:)')
print(sio.getvalue())
运行结果:
hello, python and Kitty:)
本文参考了林信良编写的《Python编程技术手册》第8章内容,有删改。