Смущение о StringIO, cStringIO и ByteIO

Я гуглил и также ищу на SO разницу между этими буферными модулями. Тем не менее, я все еще не очень хорошо понимаю, и я думаю, что некоторые из прочитанных мной постов устарели.

В 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 всегда делает новую копию, которая стоит больше времени. Но есть также некоторые сообщения, упомянутые, что это было исправлено в более поздних версиях Python.

Так может ли кто-нибудь провести тщательное сравнение этих IO в последних версиях Python 2.x и 3.x?


Некоторые из ссылок, которые я нашел:

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

    io.StringIO требует строку в кодировке Unicode. io.BytesIO требуется строка байтов. StringIO.StringIO допускает либо Unicode, либо байтовую строку. cStringIO.StringIO требует строку, которая закодирована как строка байтов.

Но cStringIO.StringIO('abc') не вызывает никаких ошибок.

  • https://review.openstack.org/#/c/286926/1

    Класс StringIO - это неправильный класс для использования, особенно если учесть, что субъединица v2 является двоичной, а не строкой.

  • http://comments.gmane.org/gmane.comp.python.devel/148717

    cStringIO.StringIO(b'data ') не копировал данные, в то время как io.BytesIO(b'data') делает копию (даже если данные не будут изменены позже).

В этом посте есть исправление в 2014 году.

  • Много SO сообщений, не перечисленных здесь.

Вот результаты 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.

Ответ 1

Вы должны использовать io.StringIO для обработки объектов unicode и io.BytesIO для обработки объектов bytes в Python 2 и 3, для прямой совместимости (это все, что 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)

В 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