конвертировать io.StringIO в io.BytesIO
Оригинальный вопрос: я получил объект StringIO, как я могу преобразовать его в BytesIO?
Обновление: более общий вопрос, как преобразовать двоичный (кодированный) файловый объект в декодированный файловый объект в python3?
Наивный подход, который я получил:
import io
sio = io.StringIO('wello horld')
bio = io.BytesIO(sio.read().encode('utf8'))
print(bio.read()) # prints b'wello horld'
Есть ли более элегантный способ сделать это?
например, для обратного вопроса (BytesIO
→ StringIO
) существует класс - io.TextIOWrapper, который делает именно это (см. этот ответ)
Ответы
Ответ 1
Это может быть в общем полезный инструмент для преобразования потока символов в поток байтов, так что здесь идет:
import io
class EncodeIO(io.BufferedIOBase):
def __init__(self,s,e='utf-8'):
self.stream=s # not raw, since it isn't
self.encoding=e
self.buf=b"" # encoded but not yet returned
def _read(self,s): return self.stream.read(s).encode(self.encoding)
def read(self,size=-1):
b=self.buf
self.buf=b""
if size is None or size<0: return b+self._read(None)
ret=[]
while True:
n=len(b)
if size<n:
b,self.buf=b[:size],b[size:]
n=size
ret.append(b)
size-=n
if not size: break
b=self._read(min((size+1024)//2,size))
if not b: break
return b"".join(ret)
read1=read
Очевидно, write
может быть определена симметрично для декодирования ввода и отправки его в основной поток, хотя тогда вам придется иметь дело с достаточным количеством байтов только для части символа.
Ответ 2
Ответ @foobarna можно улучшить, унаследовав базовый класс io
import io
sio = io.StringIO('wello horld')
class BytesIOWrapper(io.BufferedReader):
"""Wrap a buffered bytes stream over TextIOBase string stream."""
def __init__(self, text_io_buffer, encoding=None, errors=None, **kwargs):
super(BytesIOWrapper, self).__init__(text_io_buffer, **kwargs)
self.encoding = encoding or text_io_buffer.encoding or 'utf-8'
self.errors = errors or text_io_buffer.errors or 'strict'
def _encoding_call(self, method_name, *args, **kwargs):
raw_method = getattr(self.raw, method_name)
val = raw_method(*args, **kwargs)
return val.encode(self.encoding, errors=self.errors)
def read(self, size=-1):
return self._encoding_call('read', size)
def read1(self, size=-1):
return self._encoding_call('read1', size)
def peek(self, size=-1):
return self._encoding_call('peek', size)
bio = BytesIOWrapper(sio)
print(bio.read()) # b'wello horld'
Ответ 3
Как некоторые отмечали, вы должны сделать кодирование/декодирование самостоятельно.
Тем не менее, вы можете добиться этого элегантным способом - реализовать свой собственный TextIOWrapper
для string => bytes
.
Вот такой пример:
class BytesIOWrapper:
def __init__(self, string_buffer, encoding='utf-8'):
self.string_buffer = string_buffer
self.encoding = encoding
def __getattr__(self, attr):
return getattr(self.string_buffer, attr)
def read(self, size=-1):
content = self.string_buffer.read(size)
return content.encode(self.encoding)
def write(self, b):
content = b.decode(self.encoding)
return self.string_buffer.write(content)
Который производит вывод как это:
In [36]: bw = BytesIOWrapper(StringIO("some lengt˙˚hyÔstring in here"))
In [37]: bw.read(15)
Out[37]: b'some lengt\xcb\x99\xcb\x9ahy\xc3\x94'
In [38]: bw.tell()
Out[38]: 15
In [39]: bw.write(b'ME')
Out[39]: 2
In [40]: bw.seek(15)
Out[40]: 15
In [41]: bw.read()
Out[41]: b'MEring in here'
Надеюсь, это прояснит ваши мысли!
Ответ 4
Интересно, что, хотя вопрос может показаться разумным, не так-то просто найти практическую причину, по которой мне нужно преобразовать StringIO
в BytesIO
. Оба в основном являются буферами, и вам обычно требуется только один из них для дополнительных манипуляций с байтами или текстом.
Я могу ошибаться, но я думаю, что ваш вопрос на самом деле заключается в том, как использовать экземпляр BytesIO
когда некоторый код, которому вы хотите передать его, ожидает текстовый файл.
В этом случае это общий вопрос, и решением является модуль кодеков.
Два обычных случая его использования следующие:
Составьте объект файла для чтения
In [16]: import codecs, io
In [17]: bio = io.BytesIO(b'qwe\nasd\n')
In [18]: StreamReader = codecs.getreader('utf-8') # here you pass the encoding
In [19]: wrapper_file = StreamReader(bio)
In [20]: print(repr(wrapper_file.readline()))
'qwe\n'
In [21]: print(repr(wrapper_file.read()))
'asd\n'
In [26]: bio.seek(0)
Out[26]: 0
In [27]: for line in wrapper_file:
...: print(repr(line))
...:
'qwe\n'
'asd\n'
Создать объект File для записи
In [28]: bio = io.BytesIO()
In [29]: StreamWriter = codecs.getwriter('utf-8') # here you pass the encoding
In [30]: wrapper_file = StreamWriter(bio)
In [31]: print('жаба', 'цап', file=wrapper_file)
In [32]: bio.getvalue()
Out[32]: b'\xd0\xb6\xd0\xb0\xd0\xb1\xd0\xb0 \xd1\x86\xd0\xb0\xd0\xbf\n'
In [33]: repr(bio.getvalue().decode('utf-8'))
Out[33]: "'жаба цап\\n'"
Ответ 5
StringIO
наследует TextIOBase, который имеет следующее в документации по Python 3:
TextIOBase.buffer
Базовый двоичный буфер (экземпляр BufferedIOBase
), с TextIOBase
имеет дело TextIOBase
. Это не является частью API TextIOBase и может не существовать в некоторых реализациях.
Однако последнее предложение не очень многообещающе, и на самом деле CPython, похоже, не поддерживает его:
>>> io.TextIOBase()
<io.TextIOBase object at 0x00000277468BC0B8>
>>> io.TextIOBase().buffer
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'TextIOBase' object has no attribute 'buffer'
Основная ветвь исходного кода CPython на данный момент, когда я пишу это, похоже, тоже не предоставляет buffer
. Поскольку это единственный способ получить доступ к внутреннему двоичному состоянию, я не думаю, что есть лучший подход, чем тот, который вы упомянули.
Ответ 6
bio
из вашего примера - _io.BytesIO
класса _io.BytesIO
. Вы использовали 2 раза функцию read()
.
Я придумал преобразование bytes
и один метод read()
:
sio = io.StringIO('wello horld')
b = bytes(sio.read(), encoding='utf-8')
print(b)
Но второй вариант должен быть еще быстрее:
sio = io.StringIO('wello horld')
b = sio.read().encode()
print(b)