I am using SWIG to generate Python bindings for a C library. The library defines a struct with value semantics. In C++ terminology the struct would be POD - copying it with memcpy
produces a semantically correct copy.
clib.h:
struct s
{
int i;
};
I compile this into a Python module using SWIG. Details on the build process are in an "appendix" of this question.
In C code, we can verify that the assignment operator for variables of struct type have value semantics:
#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);
}
In the Python wrapper, as expected, we instead have reference semantics:
import clib
s1 = clib.s()
s1.i = 100
s2 = s1
assert s2.i == 100
s2.i = 101
assert s1.i == 101
If our library were written in C++ instead, and s
had a copy constructor, SWIG would generate one for the Python wrapper too (reference). We could write:
s3 = clib.s(s1)
but for a C library, this wrapper is not generated:
TypeError: __init__() takes exactly 1 argument (2 given)
We might hope that SWIG generates the appropriate magic methods for copy.deepcopy
:
from copy import deepcopy
s4 = deepcopy(s1)
but it does not:
TypeError: can't pickle SwigPyObject objects
I am perplexed. This seems like it should be really simple, but I can find nothing. In 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 closest question on Stack Overflow has a horrifically complicated answer.
Define a trivial SWIG interface file clib.i
:
%module clib
%{
#include "clib.h"
%}
%include "clib.h"
Create a Python module using 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])
Build the whole thing with a Makefile
:
swig: setup.py clib_wrap.c
python2 setup.py build_ext --inplace
clib_wrap.c: clib.i
swig -python clib.i
Then, compile/run the Python and C test programs exactly as listed above.
While a C library can't have constructors/destructors, you can define them after-the-fact for a SWIG wrapper ref: swig docs 5.5.6. Note that constructors have to be written carefully:
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);
}
}
Use case:
>>> 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