我有一些想要被解析为流的字节数据,因为序列中较早的字节控制着下游字节的解释。所以BytesIO看起来像我想要的东西。但我也想使用struct模块提供的功能。但是struct的接口不是流式的。是否有一种聪明/惯用的方式将两者结合在一起?
举例来说,这是一个示例数据块:
b'\n\x00\x02\x90\x10\x00\n\x00\x02`\x10\x00\n\x00\x02\x80\x10\x00'
我想将前4个字节作为无符号大端int(例如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:]
...
但这似乎不太优雅。
答案 0 :(得分:8)
我通常做的是:
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或者您获得足够的字节数。否则,这就是你所需要的。
答案 1 :(得分:1)
使用struct
缓冲区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('…')