ctypes的RSHIFT为c_ulong的重新实现(ctypes reimplementation

2019-07-22 16:28发布

我正在访问通过ctypes的一个C库和我坚持以下问题:

我生成一个“包装”(ctypes的命令来访问与ctypes的库),使用ctypeslib。 C库包含被转换为Python函数在该步骤中的宏。 (独立于库内部尽可能我想使用一些蟒蛇这些宏的)。

其中一个宏是这样的:

# using the ctypes types
myuint16_t = c_ushort
myuint32_t = c_ulong

def mymacro(x): return (myuint16_t)((myuint32_t)(x) >> 16) # macro

我想用下面的方式一个单独的模块中生成的函数(函数内部):

return wrapper.mymacro(valueToBeConverted) # valueToBeConverted is an int

但是,使用此行中我得到了以下错误:

....   
def mymacro(x): return (myuint16_t)((myuint32_t)(x) >> 16) # macro
TypeError: unsupported operand type(s) for >>: 'c_ulong' and 'int'

(我知道,转移一个c_ulong的常见方式是c_ulongvar.value >> x ,但我将不得不修补生成的包装在C库,每次有新的变化。所以我尽量避免这种情况)。

看来, __rshift__ c_ulong的实现不能在这里使用。

print c_ulong.__rshift__
# throws AttributeError: type object 'c_ulong' has no attribute '__rshift__'

嗯,似乎很奇怪......所以我决定重新实现__rshift__ c_ulong得到它的工作方法:

from ctypes import *
from types import MethodType

def rshift(self, val):
    print self.value >> val

# create an unbound method which applies to all (even existing) instances
c_ulong.__rshift__ = MethodType(rshift, None, c_ulong)

a = c_ulong(1)
a >> 16

不过,这并不解决问题。 我仍然得到一个错误:

a >> 16
TypeError: unsupported operand type(s) for >>: 'c_ulong' and 'int'

是否有可能在__rshift__方法只能用于同一类的两个实例? 我试过如下:

def rshift(self, val):
    print self.value >> int(val.value)

a = c_ulong(1)
a >> c_ulong(16) 

和它的作品。 但是,这也将意味着我仍然要修补生成的包装。

所以:有谁知道什么诀窍吗?

更新

@eryksun的解决方案工作。 我在用:

from ctypes import *
# from types import MethodType

def _rshift(self, other):
    if hasattr(other, 'value'):
        other = other.value
    return c_ulong(self.value >> other)

def _lshift(self, other):
    if hasattr(other, 'value'):
        other = other.value
    return c_ulong(self.value << other)

def _coerce(self, other):
    try:
        return self, self.__class__(other)
    except TypeError:
        return NotImplemented

# Add the functions to the type. A method is created when
# accessed as an attribute of an instance.
c_ulong.__lshift__ = _lshift
c_ulong.__rshift__ = _rshift
c_ulong.__coerce__ = _coerce

Answer 1:

由于_ctypes._SimpleCData类型不具有Py_TPFLAGS_CHECKTYPES标志,2.x的子类都被视为使用旧式的数字__coerce__二进制运算。 见对象/ abstract.c的通话方案,并在执行函数binary_op1

为了便于演示这个标志可以在类型的对象,你只需要定义(依稀有很多的上切换void * )到tp_flags领域。

黑客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))

接下来,创建一个unsigned long的子类,并使用from_address工厂创建一个PyTypeObject它。 获取与内置的地址id ,这是具体实现细节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'

如预期最后一步失败了。 现在设置的标志:

>>> c_ulong_type.tp_flags |= Py_TPFLAGS_CHECKTYPES

>>> a >> 2
__rshift__ c_ulong(16L) 2
c_ulong(4L)

问题解决了? 但是,这是一个黑客。 与再试一次__coerce__实现。


实施__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)

当然它失败如果c_ulong不能创建,诸如用于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'


文章来源: ctypes reimplementation of rshift for c_ulong