自引用类:从C接口创建的具体Python类

7

我正在尝试设计一个C接口,可以轻松地在Python中进行扩展(使用ctypes)。我在C中使用了自然语言习惯用法

struct format {
    int (*can_open)(const char *filename);
    struct format * (*open)(const char *filename);
    void (*delete)(struct format *self);
    int (*read)(struct format *self, char *buf, size_t len);
};

如果我想要直接从C扩展这个接口,它可以很好地工作:

struct derived /* concrete implementation */
{
    struct format base;
};

但是我真正想做的是,使用ctypes从Python实现此接口。以下是我的实现代码:
CANOPENFUNC   = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)
#OPENFUNC     = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p)
#OPENFUNC     = ctypes.CFUNCTYPE(ctypes.POINTER( python_format ), ctypes.c_char_p)
#DELETEFUNC   = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
#READFUNC     = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p)

def py_canopen_func( string ):
    print "py_canopen_func", string
    return 1

canopen_func   = CANOPENFUNC(py_canopen_func)
#open_func     = OPENFUNC(  py_open_func)
#delete_func   = DELETEFUNC(py_canopen_func)
#read_func     = READFUNC(py_canopen_func)

class python_format(ctypes.Structure):
  _fields_ = (
    ('can_open',  CANOPENFUNC),
    ('open',      OPENFUNC),
    ('delete',    DELETEFUNC),
    ('read',      READFUNC),
  )
  def __init__(self):
    self.can_open = canopen_func
    OPENFUNC    = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p)
    def py_open_func2( string ):
      print "py_open_func2", string
      return ctypes.byref(self)
    self.open   = OPENFUNC( py_open_func2 )
    #self.delete = delete_func
    #self.read = read_func

我在这里努力地定义OPENFUNC的原型。技术上来说,它应该是:

OPENFUNC    = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p)

然而,我需要先定义python_format,这又需要对OPENFUNC进行定义。

附加问题:实际上应该如何实现此函数?例如:

def func( str ): return None

或者

def func( str ): i = python_format(); return ctypes.pointer(i)

都给我:

class python_format(ctypes.Structure):
  pass
OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER( python_format ), ctypes.c_char_p)
OPENFUNC( func )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: invalid result type for callback function

这是否与另一个问题有关?如果是,那么我应该更改我的初始C设计,因为我将无法从回调中返回对python_format实例的指针吗?


正确。open 应该返回已分配的实例。理想情况下,在 Python 中,open 的实现应该简单地 return selfreturn ctypes.pointer(self)。由于垃圾回收器,delete 在 Python 中将不起作用。 - malat
2
当尝试使用非简单类型作为回调结果时,您会收到TypeError消息,这个消息并不是很有帮助。回调的结果类型必须在其StgDictObject(常规PyDictObject的ctypes扩展)中具有setfunc。此要求限制您只能使用简单类型,例如c_void_p,并且回调函数应返回ctypes.addressof(self) - Eryk Sun
2个回答

5
ctypes.Structure._fields_文档中,它解释了如何实现这个功能:

在定义Structure子类的类语句之后,可以定义_fields_类变量,这允许创建直接或间接引用自身的数据类型。

这意味着您可以添加一个:
class python_format(ctypes.Structure):  # forward declaration
    pass

在定义OPENFUNC(以及其他函数类型)之后:

OPENFUNC = ctypes.CFUNCTYPE(ctypes.POINTER( python_format ), ctypes.c_char_p)
DELETEFUNC = etc...

您需要定义python_format._fields_,可以按如下方式进行:

python_format._fields_ = (
    ('can_open',  CANOPENFUNC),
    ('open',      OPENFUNC),
    ('delete',    DELETEFUNC),
    ('read',      READFUNC),
  )

这是一个基于你的代码的更完整的示例:
import ctypes

class python_format(ctypes.Structure):  # forward declaration
    pass

CANOPENFUNC = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_char_p)
OPENFUNC = ctypes.PYFUNCTYPE(ctypes.c_int,
                                 ctypes.POINTER(python_format),
                                 ctypes.c_char_p)
DELETEFUNC = ctypes.PYFUNCTYPE(None, ctypes.c_void_p)
READFUNC = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_void_p)

def py_canopen_func(self, string):
    print "py_canopen_func", string
    return 1

def py_open_func(self, string):
    print "py_open_func2", string
    # Return types from callbacks cannot be anything other than simple
    # datatypes (c_int, c_float, ..., c_void_p). For other datatypes
    # (STRUCTURE, POINTER, ...), ctypes returns the following error
    # "Invalid result type for callback function"
    # see http://bugs.python.org/issue5710
    return 1  # can't return ctypes.byref(self)

canopen_func = CANOPENFUNC(py_canopen_func)
open_func = OPENFUNC(py_open_func)
#delete_func = DELETEFUNC(py_canopen_func)
#read_func = READFUNC(py_canopen_func)

class python_format(ctypes.Structure):
    python_format._fields_ = (
        ('can_open', CANOPENFUNC),
        ('open', OPENFUNC),
        ('delete', DELETEFUNC),
        ('read', READFUNC),
      )

    def __init__(self):
        self.can_open = canopen_func
        self.open = open_func
        #self.delete = delete_func
        #self.read = read_func

pf = python_format()

你能举个例子来展示一个使用 OPENFUNC 的 Python 函数吗? - malat
这些回调函数没有理由持有GIL。在回调到Python ctypes之前,重新获取GIL。您可以自由地使用CFUNCTYPE,并且确实应该在此处使用。另外,请参考我上面的评论,OPENFUNC结果类型可以设置为c_void_p,对于此回调,它将返回ctypes.addressof(self) - Eryk Sun

2
为了得到一个规范的答案,我将回答自己的问题,感谢@eryksun的指导。
首先要明确的是,尽管这在文档中没有明确说明,但不能从回调函数返回复杂类型。因此,无法映射C函数指针:
struct format {
    struct format * (*open)(const char *filename);
};

to

class python_format:
  pass
OPENFUNC    = ctypes.CFUNCTYPE(ctypes.POINTER(python_format), ctypes.c_char_p)
def py_canopen_func( string ):
    return None
open_func     = OPENFUNC(py_open_func)

上述代码会优雅地编译,但在运行时会出现以下错误:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: invalid result type for callback function

长话短说,当你尝试使用非简单类型作为回调结果时,你会收到一个TypeError信息,但这个信息并不是很有用。回调函数的结果类型必须在其StgDictObject中具有setfunc(这是常规PyDictObject的ctypes扩展)。这个要求限制了你只能使用简单类型,如c_void_p[...]。因此,直到问题5710得到解决,目前唯一的解决方法如下:
class python_format(ctypes.Structure):
  __self_ref = []
  def __init__(self):
    self.open      = self.get_open_func()
  # technically should be a @classmethod but since we are self-referencing
  # ourself, this is just a normal method:
  def get_open_func(self):
    def py_open_func( string ):
      python_format.__self_ref.append( self )
      return ctypes.addressof(self)
    return OPENFUNC( py_open_func )
OPENFUNC     = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p)

# delay init required because `read_info` requires a forward declaration:
python_format._fields_ = (
    ('open',      OPENFUNC),
  )

在使用_fields_之后对其进行赋值是无效的。而且似乎规则在这方面已经发生了变化。 - Antti Haapala -- Слава Україні

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接