打包格式字符串中的自动重复标志

5 投票
2 回答
2908 浏览
提问于 2025-04-17 04:51

在PHP中,unpack()函数有一个“*”标志,意思是“重复这个格式直到输入结束”。比如,这样可以打印出97、98、99。

$str = "abc";
$b = unpack("c*", $str);
print_r($b);

在Python中有没有类似的功能呢?当然,我可以这样做:

str = "abc"
print struct.unpack("b" * len(str), str)

但我在想有没有更好的方法。

2 个回答

6

在Python 3.4及以后的版本中,你可以使用一个新功能 struct.iter_unpack

struct.iter_unpack(fmt, buffer)

这个函数可以根据格式字符串fmt,从缓冲区buffer中逐步提取数据。它会返回一个迭代器,这个迭代器会从缓冲区中读取相同大小的数据块,直到所有内容都被读取完。缓冲区的字节大小必须是格式所需大小的整数倍,这个大小可以通过calcsize()来计算。

每次迭代都会返回一个元组,内容根据格式字符串来决定。

假设我们想要解包一个数组 b'\x01\x02\x03'*3,使用的格式字符串是 '<2sc'(表示两个字符后跟一个字符,重复直到结束)。

使用 iter_unpack,你可以这样做:

>>> import struct
>>> some_bytes = b'\x01\x02\x03'*3
>>> fmt = '<2sc'
>>> 
>>> tuple(struct.iter_unpack(fmt, some_bytes))
((b'\x01\x02', b'\x03'), (b'\x01\x02', b'\x03'), (b'\x01\x02', b'\x03'))

如果你想把这个结果展开,可以使用 itertools.chain.from_iterable

>>> from itertools import chain
>>> tuple(chain.from_iterable(struct.iter_unpack(fmt, some_bytes)))
(b'\x01\x02', b'\x03', b'\x01\x02', b'\x03', b'\x01\x02', b'\x03')

当然,你也可以用嵌套的列表推导式来实现同样的效果。

>>> tuple(x for subtuple in struct.iter_unpack(fmt, some_bytes) for x in subtuple)
(b'\x01\x02', b'\x03', b'\x01\x02', b'\x03', b'\x01\x02', b'\x03')
4

struct.unpack这个功能里并没有直接提供这样的功能,但我们可以自己定义一个这样的函数:

import struct

def unpack(fmt, astr):
    """
    Return struct.unpack(fmt, astr) with the optional single * in fmt replaced with
    the appropriate number, given the length of astr.
    """
    # http://stackoverflow.com/a/7867892/190597
    try:
        return struct.unpack(fmt, astr)
    except struct.error:
        flen = struct.calcsize(fmt.replace('*', ''))
        alen = len(astr)
        idx = fmt.find('*')
        before_char = fmt[idx-1]
        n = (alen-flen)/struct.calcsize(before_char)+1
        fmt = ''.join((fmt[:idx-1], str(n), before_char, fmt[idx+1:]))
        return struct.unpack(fmt, astr)

print(unpack('b*','abc'))
# (97, 98, 99)

撰写回答