web-dev-qa-db-ja.com

StringIO、cStringIO、ByteIOについて混乱する

私はグーグルで検索し、SOでこれらのバッファモジュールの違いを検索しました。しかし、私はまだよく理解しておらず、読んだ記事のいくつかは古くなっていると思います。

Python 2.7.11では、r = requests.get(url)を使用して特定の形式のバイナリファイルをダウンロードしました。その後、StringIO.StringIO(r.content)cStringIO.StringIO(r.content)、およびio.BytesIO(r.content)をコンテンツの解析用に設計された関数に渡しました。

これら3つの方法はすべて使用可能です。つまり、ファイルがバイナリであっても、StringIOを使用することはまだ可能です。どうして?

もう1つは、効率性に関するものです。

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が常に新しいコピーを作成し、それがより多くの時間を要すると言ったことを見つけました。しかし、これは後のPythonバージョンで修正されたという言及もあります。

だから、最新のPython 2.xと3.xの両方で、これらのIOsを徹底的に比較できますか?


私が見つけた参考文献のいくつか:

  • https://trac.edgewall.org/ticket/12046

    io.StringIOにはUnicode文字列が必要です。 io.BytesIOにはバイト文字列が必要です。 StringIO.StringIOは、ユニコードまたはバイト文字列を許可します。 cStringIO.StringIOには、バイト文字列としてエンコードされた文字列が必要です。

ただし、cStringIO.StringIO('abc')はエラーを発生させません。

2014年のこの投稿には修正パッチがあります。

  • たくさんのSOここにリストされていない投稿。

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.StringIOStringIO.StringIOioよりもはるかに効率的です。

21
Lee

unicodeオブジェクトの処理には_io.StringIO_を使用し、前方互換性の場合はpython 2および3の両方でbytesオブジェクトの処理に_io.BytesIO_を使用する必要があります(これは3つすべてが提供するものです)。


以下は、numpyからstr/bytesへの変換コストを含まない、より良いテスト(python 2および3))です。

_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)
_

python 2では、テストすることもできます:

_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
_
19
Eric