Stickum

Python code pasted @ 16:02 on Sat, 24 Jan 09
Copy & Paste Plain Text
1
#!/usr/bin/env python
2
3
"""
4
Here's the problem I ran into.  ctypes documentation claims:
5
6
        It is possible to defined sub-subclasses of structures, they inherit
7
        the fields of the base class. If the subclass definition has a
8
        separate _fields_ variable, the fields specified in this are
9
        appended to the fields of the base class.
10
11
        Structure and union constructors accept both positional and keyword
12
        arguments. Positional arguments are used to initialize member fields
13
        in the same order as they are appear in _fields_. Keyword arguments
14
        in the constructor are interpreted as attribute assignments, so they
15
        will initialize _fields_ with the same name, or create new attributes
16
        for names not present in _fields_.
17
18
And changes in 0.9.5 indicates
19
20
        The semantics of the _fields_ attribute in sub-subclasses of
21
        Structure and Union has been fixed. The baseclasses _fields_ list
22
        is extended, not replaced, in subclasses. Assigning _fields_ when
23
        it is no longer possible raises an error now.
24
25
Unfortunately, the implementation doesn't behave exactly as I would
26
expect.  This runnable module details the problem.
27
"""
28
29
"""
30
So here's what I want to do:
31
"""
32
import ctypes
33
from traceback import print_exc
34
35
class MyStruct(ctypes.Structure):
36
        _fields_ = [
37
                ('id', ctypes.c_long),
38
                ('name', ctypes.c_wchar*32),
39
        ]
40
41
class MySubStruct1(MyStruct):
42
        """
43
        Because the structure of MySubStruct extends
44
        the structure of MyStruct, this structure
45
        can be used as a drop-in replacement for calls where the
46
        structure is passed by reference.
47
        """
48
        _fields_ = [
49
                ('special_name', ctypes.c_wchar*128),
50
        ]
51
52
53
"""An sure enough, this works great in all cases except where
54
initializing from positional arguments.
55
"""
56
57
try:
58
        MySubStruct1(5, "ashbury")
59
        raise RuntimeError('expected TypeError or ValueError but ran fine')
60
except ValueError:
61
        "too many initializers (python 2.5)"
62
except TypeError:
63
        "too many initializers (python 2.6)"
64
65
# hmm.  It seems I can only initialize with positional arguments
66
#  in the sub-sub class
67
res = MySubStruct1("param")
68
assert res.special_name == 'param' and res.name == ''
69
70
try:
71
        # maybe the structure expects the subclass params first
72
        res = MySubStruct1("param", 5)
73
        raise RuntimeError("expected TypeError or ValueError but ran fine")
74
except ValueError:
75
        "too many initializers (python 2.5)"
76
except TypeError:
77
        "too many initializers (python 2.6)"
78
79
"""
80
It seems the base structure attributes cannot be set by positional
81
arguments.
82
"""
83
84
"I thought adding this to the subclass would address the issue"
85
86
class MySubStruct2(MyStruct):
87
        _fields_ = [
88
                ('special_name', ctypes.c_wchar*128),
89
        ]
90
91
        def __init__(self, *args, **kwargs):
92
                super_self = super(MySubStruct2, self)
93
                super_fields = super_self._fields_
94
                super_args = args[:len(super_fields)]
95
                self_args = args[len(super_fields):]
96
                super_self.__init__(*super_args)
97
                ctypes.Structure.__init__(self, *self_args, **kwargs)
98
99
"""
100
but that didn't work because super_self.__init__ still gets the field
101
names from the subclass.
102
"""
103
104
try:
105
        MySubStruct2(5, "downy")
106
        raise RuntimeError('expected TypeError or ValueError but ran fine')
107
except ValueError:
108
        "too many initializers (python 2.5)"
109
except TypeError:
110
        "too many initializers (python 2.6)"
111
        
112
"""
113
Then I tried adding a property, hoping that that would not be an
114
issue during class creation.
115
"""
116
117
try:
118
        class MySubStruct3(MyStruct):
119
                _fields_ = [
120
                        ('special_name', ctypes.c_wchar*128),
121
                ]
122
123
                @property
124
                def _fields_(self):
125
                        return DYNAMIC_TIME_ZONE_INFORMATION._fields_ + super(DYNAMIC_TIME_ZONE_INFORMATION)._fields_
126
        raise RuntimeError('expected TypeError but ran fine')
127
except TypeError:
128
        "_fields_ must be a sequence of pairs"
129
130
"""
131
I then tried reassigning the _fields_ member of the subclass after it
132
had been created... 
133
"""
134
class MySubStruct4(MyStruct):
135
        _fields_ = [
136
                ('special_name', ctypes.c_wchar*128),
137
        ]
138
139
try:
140
        MySubStruct4._fields_ = MyStruct._fields_ + MySubStruct4._fields_
141
        raise RuntimeError('expected AttributeError but ran fine')
142
except AttributeError:
143
        "_fields_ attribute is final"
144
145
"""Finally, I settled on this:"""
146
147
class MySubStructFinal(MyStruct):
148
        _fields_ = [
149
                ('special_name', ctypes.c_wchar*128),
150
        ]
151
152
        def __init__(self, *args, **kwargs):
153
                """Allow initialization from args from both this class and
154
                its superclass.  Default ctypes implementation seems to
155
                assume that this class is only initialized with its own
156
                _fields_ (for non-keyword-args)."""
157
                super_self = super(MySubStructFinal, self)
158
                super_fields = super_self._fields_
159
                super_args = args[:len(super_fields)]
160
                self_args = args[len(super_fields):]
161
                # convert the super args to keyword args so they're also handled
162
                for field, arg in zip(super_fields, super_args):
163
                        field_name, spec = field
164
                        kwargs[field_name] = arg
165
                super(MySubStructFinal, self).__init__(*self_args, **kwargs)
166
                
167
s = MySubStructFinal(6, 'driver', 'module')
168
169
assert s.name == 'driver' and s.special_name == 'module'