混淆 StringIO、StringIO 和 BytesIO
Confusing about StringIO, cStringIO and ByteIO
我用谷歌搜索并在 SO 上搜索这些缓冲模块之间的区别。但是,我仍然不是很了解,我认为我阅读的一些 post 已经过时了。
在Python 2.7.11中,我使用r = requests.get(url)
下载了一个特定格式的二进制文件。然后我将 StringIO.StringIO(r.content)
、cStringIO.StringIO(r.content)
和 io.BytesIO(r.content)
传递给一个为解析内容而设计的函数。
这三种方法都可以。我的意思是,即使文件是二进制文件,使用 StringIO
仍然可行。为什么?
另一件事是关于他们的效率。
In [1]: import StringIO, cStringIO, io
In [2]: from numpy import random
In [3]: x = random.random(1000000)
In [4]: %timeit y = cStringIO.StringIO(x)
1000000 loops, best of 3: 736 ns per loop
In [5]: %timeit y = StringIO.StringIO(x)
1000 loops, best of 3: 283 µs per loop
In [6]: %timeit y = io.BytesIO(x)
1000 loops, best of 3: 1.26 ms per loop
如上图所示,cStringIO > StringIO > BytesIO
。
我发现有人提到 io.BytesIO
总是制作一份新副本,这会花费更多时间。但也有一些 post 提到这在后来的 Python 版本中得到了修复。
所以,谁能对这些 IO
进行彻底比较,在最新的 Python 2.x 和 3.x 中?
我找到的一些参考:
- https://trac.edgewall.org/ticket/12046
io.StringIO requires a unicode string. io.BytesIO requires a bytes string. StringIO.StringIO allows either unicode or bytes string. cStringIO.StringIO requires a string that is encoded as a bytes string.
但是 cStringIO.StringIO('abc')
不会引发任何错误。
https://review.openstack.org/#/c/286926/1
The StringIO class is the wrong class to use for this, especially considering that subunit v2 is binary and not a string.
http://comments.gmane.org/gmane.comp.python.devel/148717
cStringIO.StringIO(b'data') didn't copy the data while io.BytesIO(b'data') makes a copy (even if the data is not modified later).
2014 年 post 中有修复补丁。
- 许多 SO post未在此处列出。
这是 Eric 示例的 Python 2.7 结果
%timeit cStringIO.StringIO(u_data)
1000000 loops, best of 3: 488 ns per loop
%timeit cStringIO.StringIO(b_data)
1000000 loops, best of 3: 448 ns per loop
%timeit StringIO.StringIO(u_data)
1000000 loops, best of 3: 1.15 µs per loop
%timeit StringIO.StringIO(b_data)
1000000 loops, best of 3: 1.19 µs per loop
%timeit io.StringIO(u_data)
1000 loops, best of 3: 304 µs per loop
# %timeit io.StringIO(b_data)
# error
# %timeit io.BytesIO(u_data)
# error
%timeit io.BytesIO(b_data)
10000 loops, best of 3: 77.5 µs per loop
至于 2.7,cStringIO.StringIO
和 StringIO.StringIO
比 io
效率高得多。
在 python 2 和 3 中,您应该使用 io.StringIO
来处理 unicode
对象,并使用 io.BytesIO
来处理 bytes
对象,以实现向前兼容性(这是所有 3 个必须提供的)。
这是一个更好的测试(针对 python 2 和 3),它不包括从 numpy 到 str
/bytes
的转换成本
import numpy as np
import string
b_data = np.random.choice(list(string.printable), size=1000000).tobytes()
u_data = b_data.decode('ascii')
u_data = u'\u2603' + u_data[1:] # add a non-ascii character
然后:
import io
%timeit io.StringIO(u_data)
%timeit io.StringIO(b_data)
%timeit io.BytesIO(u_data)
%timeit io.BytesIO(b_data)
在python2,还可以测试:
import StringIO, cStringIO
%timeit cStringIO.StringIO(u_data)
%timeit cStringIO.StringIO(b_data)
%timeit StringIO.StringIO(u_data)
%timeit StringIO.StringIO(b_data)
其中一些会崩溃,抱怨非 ascii 字符
Python 3.5 个结果:
>>> %timeit io.StringIO(u_data)
100 loops, best of 3: 8.61 ms per loop
>>> %timeit io.StringIO(b_data)
TypeError: initial_value must be str or None, not bytes
>>> %timeit io.BytesIO(u_data)
TypeError: a bytes-like object is required, not 'str'
>>> %timeit io.BytesIO(b_data)
The slowest run took 6.79 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 344 ns per loop
Python 2.7 结果(运行 在另一台机器上):
>>> %timeit io.StringIO(u_data)
1000 loops, best of 3: 304 µs per loop
>>> %timeit io.StringIO(b_data)
TypeError: initial_value must be unicode or None, not str
>>> %timeit io.BytesIO(u_data)
TypeError: 'unicode' does not have the buffer interface
>>> %timeit io.BytesIO(b_data)
10000 loops, best of 3: 77.5 µs per loop
>>> %timeit cStringIO.StringIO(u_data)
UnicodeEncodeError: 'ascii' codec cant encode character u'\u2603' in position 0: ordinal not in range(128)
>>> %timeit cStringIO.StringIO(b_data)
1000000 loops, best of 3: 448 ns per loop
>>> %timeit StringIO.StringIO(u_data)
1000000 loops, best of 3: 1.15 µs per loop
>>> %timeit StringIO.StringIO(b_data)
1000000 loops, best of 3: 1.19 µs per loop
我用谷歌搜索并在 SO 上搜索这些缓冲模块之间的区别。但是,我仍然不是很了解,我认为我阅读的一些 post 已经过时了。
在Python 2.7.11中,我使用r = requests.get(url)
下载了一个特定格式的二进制文件。然后我将 StringIO.StringIO(r.content)
、cStringIO.StringIO(r.content)
和 io.BytesIO(r.content)
传递给一个为解析内容而设计的函数。
这三种方法都可以。我的意思是,即使文件是二进制文件,使用 StringIO
仍然可行。为什么?
另一件事是关于他们的效率。
In [1]: import StringIO, cStringIO, io
In [2]: from numpy import random
In [3]: x = random.random(1000000)
In [4]: %timeit y = cStringIO.StringIO(x)
1000000 loops, best of 3: 736 ns per loop
In [5]: %timeit y = StringIO.StringIO(x)
1000 loops, best of 3: 283 µs per loop
In [6]: %timeit y = io.BytesIO(x)
1000 loops, best of 3: 1.26 ms per loop
如上图所示,cStringIO > StringIO > BytesIO
。
我发现有人提到 io.BytesIO
总是制作一份新副本,这会花费更多时间。但也有一些 post 提到这在后来的 Python 版本中得到了修复。
所以,谁能对这些 IO
进行彻底比较,在最新的 Python 2.x 和 3.x 中?
我找到的一些参考:
- https://trac.edgewall.org/ticket/12046
io.StringIO requires a unicode string. io.BytesIO requires a bytes string. StringIO.StringIO allows either unicode or bytes string. cStringIO.StringIO requires a string that is encoded as a bytes string.
但是 cStringIO.StringIO('abc')
不会引发任何错误。
https://review.openstack.org/#/c/286926/1
The StringIO class is the wrong class to use for this, especially considering that subunit v2 is binary and not a string.
http://comments.gmane.org/gmane.comp.python.devel/148717
cStringIO.StringIO(b'data') didn't copy the data while io.BytesIO(b'data') makes a copy (even if the data is not modified later).
2014 年 post 中有修复补丁。
- 许多 SO post未在此处列出。
这是 Eric 示例的 Python 2.7 结果
%timeit cStringIO.StringIO(u_data)
1000000 loops, best of 3: 488 ns per loop
%timeit cStringIO.StringIO(b_data)
1000000 loops, best of 3: 448 ns per loop
%timeit StringIO.StringIO(u_data)
1000000 loops, best of 3: 1.15 µs per loop
%timeit StringIO.StringIO(b_data)
1000000 loops, best of 3: 1.19 µs per loop
%timeit io.StringIO(u_data)
1000 loops, best of 3: 304 µs per loop
# %timeit io.StringIO(b_data)
# error
# %timeit io.BytesIO(u_data)
# error
%timeit io.BytesIO(b_data)
10000 loops, best of 3: 77.5 µs per loop
至于 2.7,cStringIO.StringIO
和 StringIO.StringIO
比 io
效率高得多。
在 python 2 和 3 中,您应该使用 io.StringIO
来处理 unicode
对象,并使用 io.BytesIO
来处理 bytes
对象,以实现向前兼容性(这是所有 3 个必须提供的)。
这是一个更好的测试(针对 python 2 和 3),它不包括从 numpy 到 str
/bytes
import numpy as np
import string
b_data = np.random.choice(list(string.printable), size=1000000).tobytes()
u_data = b_data.decode('ascii')
u_data = u'\u2603' + u_data[1:] # add a non-ascii character
然后:
import io
%timeit io.StringIO(u_data)
%timeit io.StringIO(b_data)
%timeit io.BytesIO(u_data)
%timeit io.BytesIO(b_data)
在python2,还可以测试:
import StringIO, cStringIO
%timeit cStringIO.StringIO(u_data)
%timeit cStringIO.StringIO(b_data)
%timeit StringIO.StringIO(u_data)
%timeit StringIO.StringIO(b_data)
其中一些会崩溃,抱怨非 ascii 字符
Python 3.5 个结果:
>>> %timeit io.StringIO(u_data)
100 loops, best of 3: 8.61 ms per loop
>>> %timeit io.StringIO(b_data)
TypeError: initial_value must be str or None, not bytes
>>> %timeit io.BytesIO(u_data)
TypeError: a bytes-like object is required, not 'str'
>>> %timeit io.BytesIO(b_data)
The slowest run took 6.79 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 344 ns per loop
Python 2.7 结果(运行 在另一台机器上):
>>> %timeit io.StringIO(u_data)
1000 loops, best of 3: 304 µs per loop
>>> %timeit io.StringIO(b_data)
TypeError: initial_value must be unicode or None, not str
>>> %timeit io.BytesIO(u_data)
TypeError: 'unicode' does not have the buffer interface
>>> %timeit io.BytesIO(b_data)
10000 loops, best of 3: 77.5 µs per loop
>>> %timeit cStringIO.StringIO(u_data)
UnicodeEncodeError: 'ascii' codec cant encode character u'\u2603' in position 0: ordinal not in range(128)
>>> %timeit cStringIO.StringIO(b_data)
1000000 loops, best of 3: 448 ns per loop
>>> %timeit StringIO.StringIO(u_data)
1000000 loops, best of 3: 1.15 µs per loop
>>> %timeit StringIO.StringIO(b_data)
1000000 loops, best of 3: 1.19 µs per loop