我正在使用 SWIG 为一个 C 库生成 Python 绑定。该库定义了一个具有值语义的结构。在 C++ 术语中,这个结构应该是 POD - 将它用 memcpy
产生一个语义正确的副本。
clib.h。
struct s
{
int i;
};
我用SWIG将其编译成Python模块。构建过程的细节在本题的 "附录 "中。
在C代码中,我们可以验证结构类型的变量的赋值操作符是否具有值语义。
#include <assert.h>
#include "clib.h"
int main()
{
struct s s1;
s1.i = 100;
struct s s2 = s1;
assert(s2.i == 100);
s2.i = 101;
assert(s1.i == 100);
}
在Python包装器中,正如我们所期望的那样,我们反而有引用语义。
import clib
s1 = clib.s()
s1.i = 100
s2 = s1
assert s2.i == 100
s2.i = 101
assert s1.i == 101
如果我们的库是用C++写的,并且 s
有一个复制构造函数,SWIG也会为Python包装器生成一个(参考). 我们可以写。
s3 = clib.s(s1)
但对于一个C库来说,这个封装器是不会生成的。
TypeError: __init__() takes exactly 1 argument (2 given)
我们可能会希望SWIG能够生成相应的神奇方法,用于 copy.deepcopy
:
from copy import deepcopy
s4 = deepcopy(s1)
但它没有。
TypeError: can't pickle SwigPyObject objects
我很困惑。这似乎应该很简单,但我什么也找不到。在 SWIG和Python "文档拷贝 "这个词只出现在前面链接的C++拷贝构造函数的说明中,以及一些关于生成的包装代码的低级细节中。拷贝 "这个词只出现在前面链接的C++拷贝构造函数的说明中,以及一些关于生成的封装代码的低级细节中。最接近的问题在Stack Overflow上 有一个复杂得可怕的答案。
定义一个简单的SWIG接口文件。clib.i
:
%module clib
%{
#include "clib.h"
%}
%include "clib.h"
创建一个Python模块,使用 setup.py
:
from distutils.core import setup, Extension
clib = Extension(
"_clib",
sources=["clib_wrap.c"],
extra_compile_args=["-g"],
)
setup(name="clib", version="1.0", ext_modules=[clib])
构建整个事情与一个 Makefile
:
swig: setup.py clib_wrap.c
python2 setup.py build_ext --inplace
clib_wrap.c: clib.i
swig -python clib.i
然后,编译Python和C测试程序,完全按照上面列出的方法。
虽然一个C库不能有构造函数(constructorsdestructors),但你可以在事后为SWIG包装器定义它们 参考:Swig docs 5.5.6。. 注意,构造函数一定要认真写。
有一个与普通C++构造函数实现的微妙区别 就是尽管构造函数声明与普通C++构造函数一样,但新构造的对象必须被返回,就像构造函数声明有一个返回值一样。
test.i:
%module test
%{
#include <stdlib.h>
#include "clib.h"
%}
%include "clib.h"
%extend s {
s(int i) {
struct s* t = malloc(sizeof(struct s));
t->i = i;
return t;
}
s(struct s* o) {
struct s* t = malloc(sizeof(struct s));
t->i = o->i;
return t;
}
~s() {
free($self);
}
}
用例:
>>> import test
>>> s1 = test.s(5)
>>> s1.i
5
>>> s2 = test.s(s1) # copy
>>> s2.i
5
>>> s2.i = 7
>>> s1.i
5
>>> s2.i
7