i am accessing a C library via ctypes and i am stuck with the following problem:
I am generating a \"wrapper\" (ctypes commands to access the library with ctypes) us
Since the _ctypes._SimpleCData
type doesn't have the Py_TPFLAGS_CHECKTYPES
flag, 2.x subclasses are treated as old-style numbers that use __coerce__
in binary operations. See Objects/abstract.c for the calling scheme and the implementation in the function binary_op1
.
For demonstration purposes this flag can be toggled on the type object, which you only need to define (vaguely with a lot of void *
) up to the tp_flags
field.
PyTypeObject
from ctypes import *
import _ctypes
Py_TPFLAGS_CHECKTYPES = 1 << 4
class PyTypeObject(Structure):
_fields_ = (('ob_refcnt', c_ssize_t),
('ob_type', c_void_p),
('ob_size', c_ssize_t),
('tp_name', c_char_p),
('tp_basicsize', c_ssize_t),
('tp_itemsize', c_ssize_t),
('tp_dealloc', c_void_p),
('tp_print', c_void_p),
('tp_getattr', c_void_p),
('tp_setattr', c_void_p),
('tp_compare', c_void_p),
('tp_repr', c_void_p),
('tp_as_number', c_void_p),
('tp_as_sequence', c_void_p),
('tp_as_mapping', c_void_p),
('tp_hash', c_void_p),
('tp_call', c_void_p),
('tp_str', c_void_p),
('tp_getattro', c_void_p),
('tp_setattro', c_void_p),
('tp_as_buffer', c_void_p),
('tp_flags', c_long))
Next, create an unsigned long
subclass, and use the from_address
factory to create a PyTypeObject
for it. Get the address with built-in id
, which is an implementation detail specific to CPython:
class c_ulong(_ctypes._SimpleCData):
_type_ = "L"
def __rshift__(self, other):
print '__rshift__', self, other
if hasattr(other, 'value'):
other = other.value
return c_ulong(self.value >> other)
c_ulong_type = PyTypeObject.from_address(id(c_ulong))
>>> a = c_ulong(16)
>>> b = c_ulong(2)
>>> a >> b
__rshift__ c_ulong(16L) c_ulong(2L)
c_ulong(4L)
>>> a >> 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for >>: 'c_ulong' and 'int'
The last step failed as expected. Now set the flag:
>>> c_ulong_type.tp_flags |= Py_TPFLAGS_CHECKTYPES
>>> a >> 2
__rshift__ c_ulong(16L) 2
c_ulong(4L)
Problem solved? But that's a hack. Try again with __coerce__
implemented.
__coerce__
class c_ulong(_ctypes._SimpleCData):
_type_ = "L"
def __rshift__(self, other):
print '__rshift__', self, other
if hasattr(other, 'value'):
other = other.value
return c_ulong(self.value >> other)
def __coerce__(self, other):
print '__coerce__', self, other
try:
return self, self.__class__(other)
except TypeError:
return NotImplemented
>>> a = c_ulong(16)
>>> b = c_ulong(2)
>>> a >> 2
__coerce__ c_ulong(16L) 2
__rshift__ c_ulong(16L) c_ulong(2L)
c_ulong(4L)
>>> 16 >> b
__coerce__ c_ulong(2L) 16
__rshift__ c_ulong(16L) c_ulong(2L)
c_ulong(4L)
Of course it fails if a c_ulong
can't be created, such as for a float
:
>>> a >> 2.0
__coerce__ c_ulong(16L) 2.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for >>: 'c_ulong' and 'float'