python ctypes vs namedtuple
Question: So I have two simple ctypes struct

    
    
    class S2 (ctypes.Structure):
        _fields_ = [
        ('A2',     ctypes.c_uint16*10),
        ('B2',     ctypes.c_uint32*10),
        ('C2',     ctypes.c_uint32*10) ]
    
    
    class S1 (ctypes.Structure):
        _fields_ = [
        ('A',     ctypes.c_uint16),
        ('B',     ctypes.c_uint32),
        ('C',     S2) ]
    

Is it possible to do this same for example with namedtuple? How lists are
handled in namedtuple?

Edit:

struc.pack usage

    
    
    test_data = '0100000002000000' + 10*'01' + 10*'01' + 10*'01'
    
    S2 = collections.namedtuple('S2', ['A2', 'B2', 'C2'])
    S1 = collections.namedtuple('S1', ['A', 'B', 'REF_to_S2'])
    
    Data2 = S2._make(struct.unpack('10p10p10p', binascii.unhexlify(test_data[16:])))
    ##this is not working, because there must be 3 args..
    Data1 = S1._make(struct.unpack('ii', binascii.unhexlify(test_data[0:16])))
    

In the end i want to print data in readable format ( having key:value pairs
visible). But now i cannot figure out how should i handle that unpack
operation with two diffrent namedtuples...?

this unpack.struct operation will take care that value type issues, right ?


Answer: > Is it possible to do this same for example with namedtuple?

It depends on what you mean by "same". You can easily create `namedtuple`
types with the same fields:

    
    
    S2 = collections.namedtuple('S2', ['A2', 'B2', 'C2'])
    S1 = collections.namedtuple('S1', ['A', 'B', 'C'])
    

However, they're obviously not the same type, and won't have the same
behavior.

* * *

First, these fields are normal Python attributes (and also normal `tuple`
members), which means they don't have static types; they can hold values of
any type.

So, you can do this:

    
    
    s2 = S2([ctypes.c_uint16(i) for i in range(10)],
            [ctypes.c_uint32(i) for i in range(10)],
            [ctypes.c_uint32(i) for i in range(10)])
    s1 = S1(ctypes.c_uint16(1), ctypes.c_uint32(2), s2)
    

But you can also do this:

    
    
    s2 = S2('a', 'b', 'c')
    s1 = S1('d', 'e', s2)
    

… or even:

    
    
    s1 = S1('d', 'e', 'f')
    

And also, note that even the first example actually created `list`s of 10
`ctypes` values, not `ctypes` arrays. If you want that, you have to cast them
explicitly.

* * *

Second, `namedtuple`s are an extension of `tuple`s, which means they're
immutable, so you can't do this:

    
    
    s1.C = s2
    

* * *

And most importantly, a `namedtuple` can't be used as a `ctypes.Structure`—you
can't pass it to a C function, you have to write manual logic (e.g., around
`struct.pack`) if you want to serialize it in some specific binary format,
etc.

* * *

> How lists are handled in namedtuple?

As mentioned above, the members of a `namedtuple` aren't statically typed, and
can hold values of any type. So, they're handled the same way they are in a
`tuple`, `list`, a normal class instance, a global variable, etc. Just stick a
`list` in there and you've got a `list`.

* * *

> yes that stuct.pack is what i need. But i cannot figure out how i should
> point that the last value in s1 is reference to s2 structure.

From the `namedtuple` side, you just use an `S2` instance as the value for the
`S1.C`, as in my examples above. Again, the items/attributes of a `namedtuple`
are just like any other attributes/variables/etc. in Python, just names that
hold references to objects. So, `s1 = S1(1, 2, s2)` will make the third item
of `s1` into another reference to the same object that `s2` refers to.

As for how to use `struct` to serialize the data: The `struct` module doesn't
have any way to directly delegate to an embedded object. But since the output
of `pack` is just a `bytes` (or, in Python 2.x, `str`) object, you can do this
with normal string manipulation:

    
    
    # version 1
    s2_struct = struct.Struct('!HII')
    s1_header = struct.Struct('!HI')
    def pack_s2(s2):
        return s2_struct.pack(s2.A2, s2.B2, s2.C2)
    def unpack_s2(s2):
        return S2._make(s2_struct.unpack(s2))
    def pack_s1(s1):
        return s1_header.pack(s1.A, s1.B) + pack_s2(s1.C)
    def unpack_S1(s1):
        offset = len(s1_header)
        a, b = s1_header.unpack(s1[:offset])
        c = unpack_s2(s1[offset:])
        return S1._make(a, b, c)
    

(I personally would use `S2(*struct.unpack` rather than `S2._make`, but since
the documentation does the latter repeatedly, I guess that has to be the
intended way to do things…)

Alternatively, you can flatten the format strings manually:

    
    
    s2_struct = struct.Struct('!HII')
    s1_struct = struct.Struct('!HIHII')
    def pack_s2(s2):
        return s2_struct.pack(s2.A2, s2.B2, s2.C2)
    def pack_s1(s1):
        return s1_struct.pack(s1.A, s1.B, s1.C.A2, s1.C.B2, s1.C.C2)
    def unpack_s2(s2):
        return S2._make(s2_struct.unpack(s2))
    def unpack_S1(s1):
        a, b, a2, b2, c2 = s1_struct.unpack(s1)
        c = S2(a2, b2, c2)
        return S1(a, b, c)
    

I think the second version is easier to read, but it's also easier to get
wrong, and requires you to think about composing objects at the binary level
instead of the Python level, so… choose whichever you find the lesser of two
evils.

