假设我从某个地方读取了一个长bytes
对象,知道它是utf-8
编码的。但是读取可能不会完全消耗可用的内容,因此流中的最后一个字符可能不完整。在此对象上调用bytes.decode()
可能会导致解码错误。但是真正失败的只是最后几个字节。在这种情况下,是否有一个函数可以工作,返回最长的解码字符串和剩余字节?
utf-8
最多将一个字符编码为4个字节,因此尝试对截断的字节进行解码应该可以,但是会浪费大量计算量,而且我不太喜欢这种解决方案。
举一个简单但具体的例子:
>>> b0 = b'\xc3\x84\xc3\x96\xc3'
>>> b1 = b'\x9c\xc3\x84\xc3\x96\xc3\x9c'
>>> (b0 + b1).decode()
>>> 'ÄÖÜÄÖÜ'
(b0 + b1).decode()
很好,但是b0.decode()
会提高。该解决方案应该能够尽可能多地解码b0
并返回无法解码的字节。
答案 0 :(得分:1)
您正在描述io.TextIOWrapper
的基本用例:二进制流上的缓冲文本流。
>>> import io
>>> txt = 'before\N{PILE OF POO}after'
>>> b = io.BytesIO(txt.encode('utf-8'))
>>> t = io.TextIOWrapper(b)
>>> t.read(5)
'befor'
>>> t.read(1)
'e'
>>> t.read(1)
''
>>> t.read(1)
'a'
与直接读取字节流相反,在这种情况下,有可能在编码的poo堆中途读取:
>>> b.seek(0)
0
>>> b.read(5)
b'befor'
>>> b.read(1)
b'e'
>>> b.read(1)
b'\xf0'
>>> b.read(1)
b'\x9f'
>>> b.read(1)
b'\x92'
>>> b.read(1)
b'\xa9'
>>> b.read(1)
b'a'
如果要明确,请指定encoding="utf-8"
。无论如何,默认编码locale.getpreferredencoding(False)
通常通常是utf-8。
答案 1 :(得分:1)
正如我在@wim答案下的评论中提到的那样,我认为您可以使用codecs.iterdecode()
增量解码器来执行此操作。由于它是一个生成器函数,因此无需在两次迭代调用之间手动保存和恢复其状态。
以下是如何处理您所描述的情况的方法:
import codecs
from random import randint
def reader(sequence):
""" Yield random length chunks of sequence until exhausted. """
plural = lambda word, n, ending='s': (word+ending) if n > 1 else word
i = 0
while i < len(sequence):
size = randint(1, 4)
chunk = sequence[i: i+size]
hexrepr = '0x' + ''.join('%02X' % b for b in chunk)
print('read {} {}: {}'.format(size, plural('byte', len(chunk)), hexrepr))
yield chunk
i += size
bytes_obj = b'\xc3\x84\xc3\x96\xc3\x9c\xc3\x84\xc3\x96\xc3\x9c' # 'ÄÖÜÄÖÜ'
for decoded in codecs.iterdecode(reader(bytes_obj), 'utf-8'):
print(decoded)
示例输出:
read 3 bytes: 0xC384C3
Ä
read 1 byte: 0x96
Ö
read 1 byte: 0xC3
read 3 bytes: 0x9CC384
ÜÄ
read 2 bytes: 0xC396
Ö
read 4 bytes: 0xC39C
Ü