问题
I am trying to "monkey patch" a class derived from Python ctypes "Union", but I am unable to do so - getting weird errors and sometimes seg-faults. The same thing works quite well when deriving from ctypes "Structure".
I have narrowed this to down to the simplest possible test case which I am posting below. I am using Python 3.6.4. I am wondering if I am doing something wrong (or is there a problem with the ctypes "Union" implementation?). Please see the code below and the corresponding output.
import ctypes
def display(self):
""" A new kind of display """
return f'Print Type #2: ( {self.y1}, {self.y2} )'
class MyStruct(ctypes.Structure):
_fields_ = [
('y1', ctypes.c_uint32),
('y2', ctypes.c_uint32)
]
def __str__(self):
return f'Print Type #1: [ {self.y1}, {self.y2} ]'
class MyUnion(ctypes.Union):
_fields_ = [
('y1', ctypes.c_uint32),
('y2', ctypes.c_uint32)
]
def __str__(self):
return f'Print Type #1: [ {self.y1}, {self.y2} ]'
if __name__ == '__main__':
a = MyStruct()
a.y1 = 10
a.y2 = 20
print('Using Structure:')
print('----------------')
print(a)
print('Original :', MyStruct.__str__)
# monkey patch __str__ with a different function.
MyStruct.__str__ = display
print('Patched :', MyStruct.__str__)
print('Patched (dict) :', MyStruct.__dict__['__str__'])
print(a)
a = MyUnion()
a.y1 = 10
a.y2 = 20
print('Using Union:')
print('------------')
print(a)
print('Original :', MyUnion.__str__)
# monkey patch __str__ with a different function.
MyUnion.__str__ = display
print('Patched :', MyUnion.__str__)
print('Patched (dict) :', MyUnion.__dict__['__str__'])
print(a)
Here's the output when I run the program.
Using Structure:
----------------
Print Type #1: [ 10, 20 ]
Original : <function MyStruct.__str__ at 0x7fdf89d02e18>
Patched : <function display at 0x7fdf8b0ebe18>
Patched (dict) : <function display at 0x7fdf8b0ebe18>
Print Type #2: ( 10, 20 )
Using Union:
------------
Print Type #1: [ 20, 20 ]
Original : <function MyUnion.__str__ at 0x7fdf89d02f28>
Patched : <function MyUnion.__str__ at 0x7fdf89d02f28>
Patched (dict) : <function display at 0x7fdf8b0ebe18>
Traceback (most recent call last):
File "ctypes_bug.py", line 54, in <module>
print(a)
TypeError: 'managedbuffer' object is not callable
Clearly, I am able to "patch" __str__
when the corresponding Python object was derived from "Structure", but I am unable to "patch" __str__
when the corresponding Python object was derived from "Union".
Interestingly, MyUnion.__dict__[__str__]
and MyUnion.__str__
shows different results - which is weird as well.
Is there something I am doing wrong here? I highly appreciate any help or insight!
回答1:
I think there's an actual CPython bug here. The __setattr__ implementation for type objects for struct types uses PyType_Type.tp_setattro
:
static int
PyCStructType_setattro(PyObject *self, PyObject *key, PyObject *value)
{
/* XXX Should we disallow deleting _fields_? */
if (-1 == PyType_Type.tp_setattro(self, key, value))
return -1;
if (value && PyUnicode_Check(key) &&
_PyUnicode_EqualToASCIIString(key, "_fields_"))
return PyCStructUnionType_update_stgdict(self, value, 1);
return 0;
}
but the one for type objects for union types uses PyObject_GenericSetAttr
:
static int
UnionType_setattro(PyObject *self, PyObject *key, PyObject *value)
{
/* XXX Should we disallow deleting _fields_? */
if (-1 == PyObject_GenericSetAttr(self, key, value))
return -1;
if (PyUnicode_Check(key) &&
_PyUnicode_EqualToASCIIString(key, "_fields_"))
return PyCStructUnionType_update_stgdict(self, value, 0);
return 0;
}
The use of PyType_Type.tp_setattro
is necessary to update type slots and invalidate the internal type attribute cache. PyObject_GenericSetAttr
doesn't know it should do either of those things, causing potential memory corruption due to "zombie" cached attributes. It looks like the same bug was fixed in early 2008 for structs, but they forgot to handle unions.
来源:https://stackoverflow.com/questions/53563561/monkey-patching-class-derived-from-ctypes-union-doesnt-work