如何将最长的子字节解码为str?

时间:2019-01-15 22:28:12

标签: python string decode

假设我从某个地方读取了一个长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并返回无法解码的字节。

2 个答案:

答案 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
Ü