【Python系列专栏】第二十七篇 Python中的StringIO和BytesIO

StringIO和BytesIO

StringIO

很多时候,数据读写不一定是对文件进行的,我们也可以在内存中进行读写操作。

StringIO 顾名思义就是在内存中读写 str

要把 str 写入 StringIO,我们需要先创建一个 StringIO 对象,然后,像文件一样写入即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

getvalue() 方法用于获得IO流中的全部内容

读取 StringIO 的方法也和读文件类似:

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!') # 用一个str初始化StringIO
>>> while True:
...     s = f.readline()
...     if s == '': # 读取完毕,跳出循环
...         break
...     print(s.strip()) # 去掉当前行首尾的空格再打印
...
Hello!
Hi!
Goodbye!

BytesIO

StringIO 操作的只能是 str,如果要操作二进制数据,就需要使用 BytesIO

BytesIO 实现了在内存中读写 bytes,我们创建一个 BytesIO,然后写入一些 bytes

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

请注意,写入的不是 str,而是经过UTF-8编码的 bytes

StringIO 类似,读取 BytesIO

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') # 用一个bytes初始化BytesIO
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

为什么使用StringIO和BytesIO

这个问题在Stackoverflow上有回答,为什么我们不直接使用 strbytes,而要这么别扭地在内存中使用 StringIOBytesIO 呢?其实呀,主要是因为 StringIOBytesIO 都是 file-like Object,可以像文件对象那样使用,当某些库的函数支持文件对象时,我们可以传入 StringIOBytesIO,也能使用,这一点 strbytes 是没法做到的。


读写IO需要注意的地方

StringIO 为例,前面我们将到读取 StringIO 时,是先使用字符串进行初始化,然后再读取

>>> f = StringIO('Hello!\nHi!\nGoodbye!') # 用一个str初始化StringIO
>>> f.readlines()
['Hello!\n', 'Hi!\n', 'Goodbye!']

但如果我们没有进行初始化,而是对一个空的 StringIO 进行写入,然后再读取呢?这时就会像下面这样:

>>> f = StringIO()
>>> f.write('Hello!\n')
7
>>> f.write('Hi!\n')
4
>>> f.write('Goodbye!')
8
>>> f.readlines()
[]

我们发现此时读取不到写入的字符串了,这是为什么呢?其实呀,这时因为当前所处流的位置(Stream Position)在末尾,所以读取不到东西了。那怎么知道当前处在流的什么位置呢?我们可以使用 tell() 方法。对比一下:

使用字符串初始化 StringIO

>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> f.tell()
0

写入 StringIO

>>> f = StringIO()
>>> f.write('Hello!\n')
7
>>> f.write('Hi!\n')
4
>>> f.write('Goodbye!')
8
>>> f.tell()
19

可以发现,使用字符串初始化时,位置会保持在流的开头,而使用 write() 方法对流进行写入操作后,位置会移动到写入结束的地方。那有没有办法在写入以后进行读取呢?有!可以使用前面提到的 getvalue() 方法读取IO流中的全部内容,另外也可以使用 seek() 方法回到前面的某一位置,然后读取该位置后的内容:

>>> f.tell()
19
>>> f.seek(0) # 回到流的开头位置
0
>>> f.tell()
0

有时候呀,我们可能会在初始化一个 StringIO 之后,想要对其进行写入操作,这时会发生一个问题:

>>> f = StringIO('Hello!')
>>> f.getvalue()
'Hello!'
>>> f.write('Hi!')
3
>>> f.getvalue()
'Hi!lo!'

可以看到初始化的内容被写入的内容覆盖了,这显然不是我们所希望的。为什么会这样呢?其实呀,跟前面说的问题是一样的,举一反三,都是因为Stream Position引起的。初始化一个 StringIO 后,位置在流的开头,此时写入就会从流的开头写入,而不是像我们所希望的那样从流的末尾写入,稍微改动一下就好了:

>>> f = StringIO('Hello!')
>>> f.seek(0, 2) # 移动到流的末尾
6
>>> f.write('Hi!')
3
>>> f.getvalue()
'Hello!Hi!'

除了移动到流的末尾,也能移动到某个位置,看看 seek() 方法的描述:

Help on built-in function seek:

seek(pos, whence=0, /) method of _io.StringIO instance
    Change stream position.

    Seek to character offset pos relative to position indicated by whence:
        0  Start of stream (the default).  pos should be >= 0;
        1  Current position - pos must be 0;
        2  End of stream - pos must be 0.
    Returns the new absolute position.

可以看到 seek() 方法有必选参数 pos 和 可选参数 whence,前者是移动多少,后者是从哪里开始移动。whence 默认为0,也即默认从流的开头移动 pos 个位置。


小结

StringIOBytesIO 是在内存中操作 strbytes 的方法,和读写文件具有一致的接口。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrrunsen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值