将打包为位图/位串的ASCII字节字符串转换回字符串?

3
我有一个字符串,每个字符最初都是一个无符号字节,但现在存储为7位并打包到无符号字节数组中。我正在尝试找到一种快速的方法来在Python中解包这个字符串,但我编写的使用bitstring模块的函数效果很好,但非常慢。看起来像这样的东西不应该如此缓慢,但我可能做得非常低效...
这似乎是一个可能微不足道的问题,但我不知道该使用什么工具,也许已经有一个函数可以解包字符串?
from bitstring import BitArray

def unpackString(raw):
    msg = ''

    bits = BitArray(bytes=raw)
    mask = BitArray('0b01111111')

    i = 0
    while 1:
        try:
            iByte = (bits[i:i + 8] & mask).int
            # value of 0 denotes a line break
            if iByte == 0:
                msg += '\n'
            elif iByte >= 32 and iByte <= 126:
                msg += chr(iByte)
            i += 7
        except:
            break

    return msg

你能否提供一份数据样本,以确保我们为您提供可行的解决方案? - tmr232
当然,原始数据的示例位于:http://files.eas.cornell.edu/~mjs472/rawbytes.bin - Marty
此外,数据是由NWS定义的天气字符串。解压时应该看起来像“<NoWx> ... <NoAttr>”。 - Marty
现在没有时间编写代码,但您可能想要做的是迭代字节,将每两个连续的字节转换为一个单词(是的,带有重叠的字节),根据需要进行移位,然后进行掩码处理。这应该会显著提高速度。 - tmr232
在这里,将您的函数应用于原始数据样本(方法:unpackString(open("rawbytes.bin", "rb").read())),返回'\n\n \n\n\n,\n\n\n\n\n\n\n<None>\nSC.Y\nWS.W\nWS.A\n'-- 您确定这是正确的吗? - Dr. Jan-Philip Gehrcke
是的,没错。重要的部分是SC.Y WS.W和WS.A,它们表示天气条件。在我第一条评论中,我对二进制字符串的内容理解有误。 - Marty
1个回答

2
这让我花了一些时间才理解,因为您的解决方案似乎忽略了第一个数据位。考虑输入字节129 (0b10000001),我期望看到以下内容被打印:64 '1000000',但是您的代码输出1 '0000001' -- 忽略了第一个位。
bs = b'\x81' # one byte string, whose value is 129 (0x81)
arr = BitArray(bs)
mask = BitArray('0b01111111')
byte = (arr[0:8] & mask).int
print(byte, repr("{:07b}".format(byte)))

最简单的解决方案是修改您的解决方案,使用bitstring.ConstBitStream -- 我通过以下方式获得了一个数量级的速度提升。

from bitstring import ConstBitStream
def unpack_bitstream(raw):
    num_bytes, remainder = divmod(len(raw) * 8 - 1, 7)
    bitstream = ConstBitStream(bytes=raw, offset=1) # use offset to ignore leading bit
    msg = b''
    for _ in range(num_bytes):
        byte = bitstream.read("uint:7")
        if not byte:
            msg += b'\n'
        elif 32 <= byte <= 126:
            msg += bytes((byte,))
            # msg += chr(byte) # python 2
    return msg

然而,这可以很容易地使用标准库完成。这使解决方案更具可移植性,并且在我尝试的情况下,速度提高了一个数量级(我没有尝试bitstring的cython化版本)。

def unpack_bytes(raw, zero_replacement=ord("\n")):
    # use - 1 to ignore leading bit
    num_bytes, remainder = divmod(len(raw) * 8 - 1, 7)

    i = int.from_bytes(raw, byteorder="big")
    # i = int(raw.encode("hex"), 16) # python 2
    if remainder:
        # remainder means there are unused trailing bits, so remove these
        i >>= remainder

    msg = []
    for _ in range(num_bytes):
        byte = i & 127
        if not byte:
            msg.append(zero_replacement)
        elif 32 <= byte <= 126:
            msg.append(byte)
        i >>= 7
    msg.reverse()

    return bytes(msg)
    # return b"".join(chr(c) for c in msg) # python 2

我使用Python 3创建了这些方法。如果您使用的是Python 2,则需要进行一些调整。我已将它们作为注释添加在它们应该替换的行后面,并标记为python 2


我喜欢你的unpack_bytes解决方案,但是int.from_bytes在Python 2.7中也不起作用。不过你的ConstBitStream解决方案非常好。 - Marty
1
增加了一种将字节字符串转换为Python 2整数的方法。仍然具有同样的竞争力。 - Dunes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接