Python 将io.StringIO转换为io.BytesIO

Python 将io.StringIO转换为io.BytesIO,python,python-3.x,encoding,io,stream,Python,Python 3.x,Encoding,Io,Stream,原始问题:我有一个对象,如何将其转换为 更新:更一般的问题是,如何在python3中将二进制(编码)对象转换为解码对象 我得到的天真方法是: import io sio = io.StringIO('wello horld') bio = io.BytesIO(sio.read().encode('utf8')) print(bio.read()) # prints b'wello horld' 有没有更高效、更优雅的方法?上面的代码只是将所有内容读入内存,对其进行编码,而不是将数据分块传输

原始问题:我有一个对象,如何将其转换为

更新:更一般的问题是,如何在python3中将二进制(编码)对象转换为解码对象

我得到的天真方法是:

import io
sio = io.StringIO('wello horld')
bio = io.BytesIO(sio.read().encode('utf8'))
print(bio.read())  # prints b'wello horld'
有没有更高效、更优雅的方法?上面的代码只是将所有内容读入内存,对其进行编码,而不是将数据分块传输


例如,对于相反的问题(
BytesIO
->
StringIO
),存在一个类,它正好可以(参见此)

将字符流转换为字节流可能是一个非常有用的工具,因此:

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
导入io
类EncodeIO(io.BufferedIOBase):
定义初始化(self,s,e='utf-8'):
self.stream=s#不是原始的,因为它不是原始的
self.encoding=e
self.buf=b“”#已编码但尚未返回
def_read(self,s):返回self.stream.read(s.encode)(self.encodeding)
def读取(自身,大小=-1):
b=自我。buf
self.buf=b“”

如果size为None或size
bio
,则示例中的是
\u io.BytesIO
类对象。 您已经使用了两次
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)

正如一些人指出的,您需要自己进行编码/解码

但是,您可以通过一种优雅的方式实现这一点—为
string=>bytes
实现您自己的
TextIOWrapper

以下是这样一个例子:

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'
希望它能澄清你的想法

可以通过继承一些
io
基类来改进

导入io
sio=io.StringIO(‘wello horld’)
类BytesIOWrapper(io.BufferedReader):
“”“将缓冲字节流包装到TextIOBase字符串流上。”“”
定义初始化(self,text\u io\u buffer,encoding=None,errors=None,**kwargs):
super(BytesIOWrapper,self)。\uuuuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuu(文本缓冲区,**kwargs)
self.encoding=编码或文本io缓冲区编码或“utf-8”
self.errors=错误或文本\u io\u缓冲区。错误或“严格”
定义编码调用(self、方法名称、*args、**kwargs):
raw\u method=getattr(self.raw,method\u name)
val=原始方法(*args,**kwargs)
返回val.encode(self.encoding,errors=self.errors)
def读取(自身,大小=-1):
返回self.\u编码\u调用('read',size)
def read1(自身,大小=-1):
返回self.\u编码\u调用('read1',大小)
def peek(自身,大小=-1):
返回self.\u编码\u调用('peek',大小)
bio=字节IOWrapper(sio)
打印(bio.read())#b'wello-horld'

有趣的是,尽管这个问题似乎合理,但要找出一个实际的原因来解释为什么我需要将
StringIO
转换为
BytesIO
,并不是那么容易。两者基本上都是缓冲区,通常只需要其中一个就可以对字节或文本进行一些额外的操作

我可能错了,但我认为你的问题实际上是当你想传递的代码需要一个文本文件时,如何使用
BytesIO
实例

在这种情况下,这是一个常见的问题,解决方案是模块

使用它的两种常见情况如下:

组合要读取的文件对象 编写要写入的文件对象
我有完全相同的需求,所以我在
nr.utils.io
包中创建了一个
EncodedStreamReader
类。它还解决了实际读取请求的字节数而不是从包装流中读取字符数的问题

$ pip install 'nr.utils.io>=0.1.0,<1.0.0'

“更优雅”是否包括在没有批量复制的情况下自己实现它?我希望有更好的方法,如果没有的话,应该比天真的方法更好。是的。请注意,在最初的问题中,您要求BytesIO->StringIO,在更新StringIO->BytesIO中。这个例子继续使用BytesIO->StringIO.
read(size)
must read@FilipDimitrovski。这是因为你说“读取15个字节”,而实际上它读取“15个字符串字符”,其中一些字符正好是2个字节长,因此是“18个长度”。我没有说它是完美的,但至少它没有破坏编码(通过将一个有效的utf-8字符拆分为2)。这是一个示例,可以通过添加更多检查或更多方法(readline、context manager等)加以改进。UTF8并不总是单字节的。这是不正确的:
BytesIOWrapper(io.StringIO('1488;ב•דד')).read(1)
返回两个字节:
b'\xd7\x90'
@ShmulikA,是的,它返回1个“字符”。要真正返回1字节,“中间”缓冲区应该是implemented@ShmulikA:永远循环,甚至循环;编辑。当我重写缓冲区(在发布之前)时,我忘记了
break
。您需要
BytesIO
而不是
StringIO
的一个原因是可以使用
upload\u fileobj
将内存中的文件上载到S3存储桶。更多信息
OutputStreamWriter
相当于
Java
中请求的包装器。截至2021年初,Github搜索的使用量达到100万。这是为了它的“实用性”。
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'"
$ pip install 'nr.utils.io>=0.1.0,<1.0.0'
import io
from nr.utils.io.readers import EncodedStreamReader
fp = EncodedStreamReader(io.StringIO('ä'), 'utf-8')
assert fp.read(1) == b'\xc3'
assert fp.read(1) == b'\xa4'