Python C 结构的 SWIG 包装器中的深层复制
Deep copy in Python SWIG wrapper of C struct
我正在使用 SWIG 为 C 库生成 Python 绑定。该库定义了一个具有值语义的结构。在 C++ 术语中,该结构将是 POD - 使用 memcpy
复制它会产生语义正确的副本。
clib.h:
struct s
{
int i;
};
我使用 SWIG 将其编译成 Python 模块。有关构建过程的详细信息,请参见此问题的 "appendix"。
在C代码中,我们可以验证struct类型变量的赋值运算符是否具有值语义:
#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 包装器生成一个复制构造函数 (reference)。我们可以这样写:
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
我很困惑。这看起来应该很简单,但我什么也找不到。在 the "SWIG and Python" documentation, the word "copy" only appears in the previously linked note on C++ copy constructors, and in some low level details about the generated wrapper code. The 中有一个极其复杂的答案。
重现详情
定义一个简单的 SWIG 接口文件 clib.i
:
%module clib
%{
#include "clib.h"
%}
%include "clib.h"
使用 setup.py
创建一个 Python 模块:
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
然后,compile/run Python 和 C 测试程序完全如上所列。
虽然 C 库不能有 constructors/destructors,但您可以事后为 SWIG 包装器定义它们 ref: swig docs 5.5.6。注意构造函数一定要仔细写:
There is one subtle difference to a normal C++ constructor implementation though and that is although the constructor declaration is as per a normal C++ constructor, the newly constructed object must be returned as if the constructor declaration had a return value.
test.i:
%module test
%{
#include <stdlib.h>
#include "clib.h"
%}
%include "clib.h"
%extend s { // add additional methods to the s struct
s(int i) { // constructor
struct s* t = malloc(sizeof(struct s));
t->i = i;
return t;
}
s(struct s* o) { // copy constructor
struct s* t = malloc(sizeof(struct s));
t->i = o->i;
return t;
}
~s() { // destructor
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
我正在使用 SWIG 为 C 库生成 Python 绑定。该库定义了一个具有值语义的结构。在 C++ 术语中,该结构将是 POD - 使用 memcpy
复制它会产生语义正确的副本。
clib.h:
struct s
{
int i;
};
我使用 SWIG 将其编译成 Python 模块。有关构建过程的详细信息,请参见此问题的 "appendix"。
在C代码中,我们可以验证struct类型变量的赋值运算符是否具有值语义:
#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 包装器生成一个复制构造函数 (reference)。我们可以这样写:
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
我很困惑。这看起来应该很简单,但我什么也找不到。在 the "SWIG and Python" documentation, the word "copy" only appears in the previously linked note on C++ copy constructors, and in some low level details about the generated wrapper code. The
重现详情
定义一个简单的 SWIG 接口文件 clib.i
:
%module clib
%{
#include "clib.h"
%}
%include "clib.h"
使用 setup.py
创建一个 Python 模块:
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
然后,compile/run Python 和 C 测试程序完全如上所列。
虽然 C 库不能有 constructors/destructors,但您可以事后为 SWIG 包装器定义它们 ref: swig docs 5.5.6。注意构造函数一定要仔细写:
There is one subtle difference to a normal C++ constructor implementation though and that is although the constructor declaration is as per a normal C++ constructor, the newly constructed object must be returned as if the constructor declaration had a return value.
test.i:
%module test
%{
#include <stdlib.h>
#include "clib.h"
%}
%include "clib.h"
%extend s { // add additional methods to the s struct
s(int i) { // constructor
struct s* t = malloc(sizeof(struct s));
t->i = i;
return t;
}
s(struct s* o) { // copy constructor
struct s* t = malloc(sizeof(struct s));
t->i = o->i;
return t;
}
~s() { // destructor
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