【问题标题】:ctypes reimplementation of rshift for c_ulong为 c_ulong 重新实现 rshift 的 ctypes
【发布时间】:2013-03-22 12:41:49
【问题描述】:

我正在通过 ctypes 访问 C 库,但遇到以下问题:

我正在使用 ctypeslib 生成一个“包装器”(使用 ctypes 访问库的​​ ctypes 命令)。 C 库包含在此步骤中转换为 python 函数的宏。 (为了尽可能独立于库内部,我想在 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 库中发生更改时我都必须修补生成的包装器。所以我尽量避免这种情况)。

这里好像不能使用c_ulong的__rshift__实现。

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

嗯,看起来很奇怪...所以我决定重新实现 c_ulong 的 __rshift__ 方法以使其正常工作:

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

【问题讨论】:

    标签: python operator-overloading ctypes bit-shift


    【解决方案1】:

    由于_ctypes._SimpleCData 类型没有Py_TPFLAGS_CHECKTYPES 标志,2.x 子类被视为在二进制运算中使用__coerce__ 的旧式数字。调用方案和函数binary_op1中的实现见Objects/abstract.c

    出于演示目的,可以在类型对象上切换此标志,您只需在 tp_flags 字段之前定义(含糊不清地有很多 void *)。

    破解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'
    

    【讨论】:

    • 谢谢@eryksun。现在我成功地使用了 coerce 的第二个建议。
    猜你喜欢
    • 1970-01-01
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-12
    • 2020-09-26
    相关资源
    最近更新 更多