0

I tried to add some diagnostics to a nested ctypes structure but failed to do so and would like to know the reason. A bare-bones example for what works as expected:

import ctypes

class FirstStruct(ctypes.Structure):
    _fields_ = [('ch', ctypes.c_ubyte)]

f = FirstStruct()

print type(f)
print hasattr(f, 'helper')
f.helper = 'xyz'
print hasattr(f, 'helper')

These lines print what I expected:

<class '__main__.FirstStruct'>
False
True

But when I use this in another Structure it fails:

class SecondStruct(ctypes.Structure):
    _fields_ = [('first', FirstStruct)]

s = SecondStruct()

print type(s.first)
print hasattr(s.first, 'helper')
s.first.helper = 'xyz'
print hasattr(s.first, 'helper')

The above results in

<class '__main__.FirstStruct'>
False
False

Can someone please explain me the difference? (I was running it on Python 2.7.8. Mind you, I don't want to change the structure itself, but wanted to add an extra variable outside the ctypes structure.)


EDIT:

Here's a more direct example:

import ctypes

class FirstStruct(ctypes.Structure):
    _fields_ = [('ch', ctypes.c_ubyte)]

class SecondStruct(ctypes.Structure):
    _fields_ = [('first', FirstStruct)]

f = FirstStruct()
s = SecondStruct()

f.helper = 'aaa'
s.first.helper = 'bbb'
s.first.ch = 0
t = s.first
t.helper = 'ccc'
t.ch = 12

print f.helper          # aaa
print t.ch              # 12
print s.first.ch        # 12
print t.helper          # ccc
print s.first.helper    # AttributeError: 'FirstStruct' object has no attribute 'helper'

The questions are: why isn't s.first and t equivalent, and why doesn't s.first.helper trigger a warning if I can't set it after all?

Andris
  • 921
  • 7
  • 14
  • 1
    The short answer is the ctype.Structures are not mutable. Assignments of mutable objects point to the same memory location as the source. With structures, a new value is created, even though the type is preserved (FirstStruct). This means that changing the source, does not alter assigned variables. Dictionaries might do the trick for you. Have you tried using the a dict as the highest level container? – Ron Norris Sep 25 '17 at 12:17
  • @RonNorris Sorry I almost understand what you wrote... But not yet. Tried out the concept with the https://stackoverflow.com/a/4828831/501814 answer for an immutable object, but here I get attribute error when attempting to assign a new attribute (just what I'd expect from s.first if it's really immutable.) Is t a mutable version of the immutable s.first, pointing to the same ctypes memory location but from a different place where it has the 'helper' attribute, while the altered version of s.first is immediately lost because of the immutability of s? – Andris Sep 25 '17 at 12:52
  • 1
    Pretty much. `s.helper = 'sss'` will work just as it does with `t.helper = 'ccc'` because you're not messing with the original FirstStruct object -- you're actually adding an attribute to 's'. – Ron Norris Sep 25 '17 at 13:14

2 Answers2

1

In your second example, s.first is returning a copy of the internal structure. You can see this by looking at its id():

>>> id(s.first)
112955080L
>>> id(s.first)  # didn't change
112955080L
>>> f=s.first    # capture a reference the object returned
>>> id(f)        # still the same
112955080L
>>> id(s.first)  # changed!
113484232L

What was happening was the new copy being returned kept being assigned to the same address, but immediately freed. After a reference was made, the copy is at another address.

So you are creating a helper attribute, just on a temporary object.

In your first example, f refers directly to the FirstStruct instance, so you can set and read the attribute.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Thank you. One addition: the underlying ctypes structure remains the same - so ctypes.addressof(f) == ctypes.addressof(s.first), as stated by the finally found [documentation line](https://docs.python.org/2/library/ctypes.html#surprises) _"Keep in mind that retrieving sub-objects from Structure, Unions, and Arrays doesn’t copy the sub-object, instead it retrieves a wrapper object accessing the root-object’s underlying buffer."_ – Andris Sep 26 '17 at 07:37
0

If you use the copy module, you can get a current snapshot of the ctype object you create. So try:

import copy
import ctypes

class FirstStruct(ctypes.Structure):
    _fields_ = [('ch', ctypes.c_ubyte)]

f = FirstStruct()

print type(f)
print hasattr(f, 'helper')
f.helper = 'xyz'
print hasattr(f, 'helper')

t = copy.copy(f)
print hasattr(t, 'helper')
>>> True
Ron Norris
  • 2,642
  • 1
  • 9
  • 13
  • Maybe my example was too abstract. I have a huge tree of such structures and some values in one branch of it alters the way the other part must be decoded. Therefore I want to add a flag to FirstStruct while it is inside SecondStruct. (Not to a copy of it.) – Andris Sep 25 '17 at 11:29