Structure with attribute like-c in Python

Viewed 291

I want to create something like structure in C but in Python. Specifically I want to sth like that:

typedef struct __attribute__((__packed__)) 
{
    float x[4];
    uint8 y;
    int z;
} structName;

in Python. I read somewhere that I can use named tuple but want to avoid padding and I don't know how to do it in Python. Can anyone show me how to do that? I will use it in UDP communication.

2 Answers

If you want to serialize (ex:save into file or send trough network) then, you should check out struct library basically you can define how to save your data, how many bytes and which order.

You can use a class in python to represent your struct and add two function for pack it into smaller binary data and unpack it from it if you need it again

Your example should be:

import struct

class StructName():
    def __init__(self, xs=None, y=None, z=None):
        self.xs = xs
        self.y = y
        self.z = z

    def pack(self):
        return struct.pack("!4fBi", *self.xs, self.y, self.z)

    def unpack(self, packed_bytes):
        *self.xs, self.y, self.z = struct.unpack("!4fBi", packed_bytes)

if __name__ == "__main__":
    s = StructName([1.0, 0.5, 2.0, 0.], 255, 42)
    tmp = StructName()
    bytedata = s.pack()
    print("s", s.xs, s.y, s.z) # s [1.0, 0.5, 2.0, 0.0] 255 42
    print(len(bytedata), bytedata) # 21 b'\x00\x00\x80?\x00\x00\x00?\x00\x00\x00@\x00\x00\x00\x00\xff\x00\x00\x00*\x00\x00\x00'
    tmp.unpack(bytedata)  # fill data into tmp class
    tmp.xs[0] = 42.5
    print("tmp", tmp.xs, tmp.y, tmp.z) # tmp [42.5, 0.5, 2.0, 0.0] 255 42

The format string in the pack and unpack function is "!4fBi" where:

  • ! - use network byte order (bigendian)
  • 4f - four float (4*4 byte)
  • B - one unsigned char (1 byte)
  • i - one signed integer (4 byte)

For more check out this section: Format Characters

Answer to a comment in the comment section of the accepted answer

import re
import struct


def get_values_from_binary(format_str, bin_data):
    val_regex = re.compile(r"[a-zA-Z]([0-9]+)?")
    ls = struct.unpack(format_str, bin_data)
    res = []
    i = 0
    for m in val_regex.finditer(format_str):
        groups = m.groups()
        current_length = 1
        if groups is not None and groups[0] is not None:
            current_length = int(groups[0])
        res.append(ls[i:i + current_length])
        i += current_length
    return res


if __name__ == "__main__":
    f_str = "<I7f7f7f3f4ffI"
    data = [0]*7 + [0.3]*7 + [0.5]*7 + [0.9]*3 + [1.0]*4 + [2.5, 3.5, 1]
    print(data)
    bindata = struct.pack(f_str, *data)
    unpacked_data = get_values_from_binary(f_str, bindata)
    print(unpacked_data)
Related