6

我有一些字节数据想要被解析为流,因为序列中较早的字节控制下游字节的解释。所以 BytesIO 看起来像我想要的东西。但我也想使用 struct 模块提供的设施。但是 struct 的接口不是流式传输的。有没有一种聪明/惯用的方式将两者结合起来?

例如,这是一个示例数据块:

b'\n\x00\x02\x90\x10\x00\n\x00\x02`\x10\x00\n\x00\x02\x80\x10\x00'

我想将前 4 个字节提取为无符号大端整数(例如struct.unpack(fmt='>I')。因为下一个字节是 0x10,我知道应该还有一个字节,结果是 0x00。然后重新开始,读取下一个 4 (0x0A000290),清洗,冲洗,重复。每个 4 字节 id 之后的字节会触发各种下游读取(一些字节,一些短路)。

我可以做类似的事情

stream = b'\n\x00\x02\x90\x10\x00\n\x00\x02`\x10\x00\n\x00\x02\x80\x10\x00'
while stream:
    id = struct.unpack('>I', stream[:4])
    stream = stream[4:]
    ...

但这似乎不够优雅。

4

2 回答 2

9

我一般做的是:

def unpack(stream, fmt):
    size = struct.calcsize(fmt)
    buf = stream.read(size)
    return struct.unpack(fmt, buf)

例如:

>>> b = io.BytesIO(b'\n\x00\x02\x90\x10\x00\n\x00\x02`\x10\x00\n\x00\x02\x80\x10\x00')
>>> print(unpack(b, '>I'))
(167772816,)
>>> print(unpack(b, '>I'))
(268438016,)
>>> print(unpack(b, '>I'))
(39849984,)
>>> print(unpack(b, '>I'))
(167772800,)
>>> print(unpack(b, '>H'))
(4096,)

如果你想知道你是否消耗了整个流,你总是可以这样做:

buf = stream.read(1)
if buf:
    raise ValueError("Stream not consumed")

但是调用你已经在使用的同一个函数可能更简单:

>>> def ensure_finished(stream):
...     try:
...         unpack(stream, 'c')
...     except struct.error:
...         pass
...     else:
...         raise ValueError('Stream not consumed')
>>> ensure_finished(b)

如果您使用的流可能read少于请求的字节数,您将需要使用while循环来继续读取和追加,直到 EOF 或您获得足够的字节。否则,这就是您所需要的。

于 2013-07-08T22:48:04.603 回答
1

使用structs 缓冲区 API:

buf = b'\n\x00\x02…'
offset = 0
id = struct.unpack_from('>I', buf, offset); offset += 4
⋮
x = struct.unpack_from('…', buf, offset)

如果您想避免在每次操作后声明偏移量,您可以编写一个小包装器,如下所示:

class unpacker(object):
    def __init__(self, buf):
        self._buf = buf
        self._offset = 0
    def __call__(self, fmt):
        result = struct.unpack_from(fmt, self._buf, self._offset)
        self._offset += struct.calcsize(fmt)
        return result

⋮

unpack = unpacker(buf)
id = unpack('>I')
⋮
x = unpack('…')
于 2013-07-08T22:49:14.240 回答