Search code examples
pythontuplesiterable-unpacking

Python - unpack struct into multiple tuples


I'd like to know if there is a cleaner way to do the following in Python 2.7?

# Current working code!
(is_enabled,) = struct.unpack_from("<?", data)
cmd_speed = struct.unpack_from("<3h", data, 1)
tach_speed = struct.unpack_from("<3h", data, 1+2*3)

Specifically, I don't like manually keeping track of the offset into the next tuple. Ideally I'd like to be able to specify the data structure with a single format statement; Something like this:

# Hypothetical example, does not work! 
(is_enabled,), cmd_speed, tach_speed = struct.unpack("<(?),(3h),(3h)", data)

Solution

  • You could do it with one call to struct.unpack, but you'd still have to slice up the result yourself:

    import struct
    data = struct.pack('<?3h3h', True, 1,2,3,4,5,6)
    result = struct.unpack('<?3h3h', data)
    is_enabled = result[0]
    cmd_speed = result[1:4]
    tach_speed = result[4:7]
    
    print(is_enabled, cmd_speed, tach_speed)
    

    yields

    (True, (1, 2, 3), (4, 5, 6))
    

    Or, you could use this:

    import struct
    import itertools as IT
    
    def unpack_formats(fmts, data):
        data = iter(data)
        return [struct.unpack(fmt, ''.join(IT.islice(data, struct.calcsize(fmt))))
                for fmt in fmts]
    
    data = struct.pack('<?3h3h', True, 1,2,3,4,5,6)
    fmts = ('<?', '<3h', '<3h')
    (is_enabled,), cmd_speed, tach_speed = unpack_formats(fmts, data)
    print(is_enabled, cmd_speed, tach_speed)
    

    which yields

    (True, (1, 2, 3), (4, 5, 6))
    

    Although unpack_formats looks prettier, the following is actually faster (probably because there is no ''.join needed):

    def unpack_formats2(fmts, data):
        result = []
        i = 0
        for fmt in fmts:
            size = struct.calcsize(fmt)
            j = i+size
            result.append(struct.unpack(fmt, data[i:j]))
            i = j
        return result
    
    In [80]: %timeit unpack_formats(fmts, data)
    100000 loops, best of 3: 3.51 us per loop
    
    In [81]: %timeit unpack_formats2(fmts, data)
    1000000 loops, best of 3: 1.61 us per loop