自引用 class:来自 C 接口的具体 python class
Self-referencing class: concrete python class from C interface
我正在尝试设计一个可以在 Python 中轻松扩展的 C 接口(在 C:
中使用 ctypes). I've used the natural idiom
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
这与另一个 issue 相关吗?如果是这样,我是否应该更改我最初的 C 设计,因为我将无法 return 从回调中指向 python_format
实例的指针?
在 documentation for ctypes.Structure._fields_
中解释了如何做到这一点:
It is possible to define the _fields_
class variable after the class
statement that defines the Structure subclass, this allows to create
data types that directly or indirectly reference themselves
这意味着您可以添加:
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()
为了得到一个规范的答案,我会回答我自己的问题,感谢@eryksun 的指导。
首先,虽然这在 documentation 中并不清楚,但不能从回调函数中 return 复杂类型。因此,不能映射 C 函数指针:
struct format {
struct format * (*open)(const char *filename);
};
到
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
长答案是:
The TypeError message you get when trying to use a non-simple type as
the result of a callback is less than helpful. A callback's result
type has to have a setfunc in its StgDictObject (a ctypes extension of
the regular PyDictObject). This requirement restricts you to using a
simple type such as c_void_p[...]
因此,从今天开始,在 issue 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),
)
我正在尝试设计一个可以在 Python 中轻松扩展的 C 接口(在 C:
中使用 ctypes). I've used the natural idiomstruct 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
这与另一个 issue 相关吗?如果是这样,我是否应该更改我最初的 C 设计,因为我将无法 return 从回调中指向 python_format
实例的指针?
在 documentation for ctypes.Structure._fields_
中解释了如何做到这一点:
It is possible to define the
_fields_
class variable after the class statement that defines the Structure subclass, this allows to create data types that directly or indirectly reference themselves
这意味着您可以添加:
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()
为了得到一个规范的答案,我会回答我自己的问题,感谢@eryksun 的指导。
首先,虽然这在 documentation 中并不清楚,但不能从回调函数中 return 复杂类型。因此,不能映射 C 函数指针:
struct format {
struct format * (*open)(const char *filename);
};
到
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
长答案是:
The TypeError message you get when trying to use a non-simple type as the result of a callback is less than helpful. A callback's result type has to have a setfunc in its StgDictObject (a ctypes extension of the regular PyDictObject). This requirement restricts you to using a simple type such as c_void_p[...]
因此,从今天开始,在 issue 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),
)