Hi Everybody, since I would like to use Python as a high level control language in a C++ project, I'm interested in a simple way to wrap C++ objects into Python objects. However, the existing solutions (like SWIG) seem to be rather complicated. So I implemented an new tool 'Python<C++>', which is inlcuded below. Unlike previous approaches, my tool makes use of templates to automate the generation of wrapper objects. This allows to: - keep the entire wrapping tool in just 3 header files (no library or code generator required) - keep the C++ code essentially independent of Python (no inheritance from PyObject or similar required) Suppose, you want to wrap this function: double foo(double, double); To wrap it, you simply write: pyWrapFunction(foo, foo); where the first argument will become the Python identifier of the function, while the second is a pointer to the C++ function. In Python, you would call the function like this:
foo(1.0, 2.0)
Likewise, a class class Foo { public: Foo(); virtual ~Foo(); virtual int foo_method(int, int); }; is wrapped as follows: pyWrapClass(Foo, Foo); where the first argument is the Python class name, the second the C++ class name. Methods are wrapped similarly to functions: pyWrapMethod(Foo, bar, &Foo::foo_method); whith the first two arguments being the Python class and method names, and the last a pointer to a C++ member function. To wrap constructors, the argument list is given instead of a function pointer (pointers to constructors do not exist in C++): pyWrapDefaultConstructor(Foo, Foo); We use class Foo in Python like this:
x = Foo() x.foo_method(1,2); del x
Inheritance is also supported: class Bar : public Foo {}; pyWrapClass(Bar, Bar); pyDeclareBaseClass(Bar, Foo); pyWrapDefaultConstructor(Bar, Bar); Thus, the Python class Bar is declared a subclass of the Python class Foo - it inherits all methods and is convertible into a base object in an expression. (This code only compiles iff the C++ class Bar is indeed a direct or indirect subclass of the C++ class Foo). In Python, we use Bar like this:
x = Bar() x.foo_method(1,2); # inherited from Foo del x
Other features of 'Python<C++>' include support for multiple inheritance, virtual base classes, abstract classes, translation of C++ exceptions into Python exceptions, mapping of Python tuples (up to 5 members) and (homogeneous) lists onto PyWrapperTuple and PyWrapperArray. The module 'cxxmodule.cxx' included in the tar file illustrates some of these features (sorry, no documentation exists yet). I have successfully compiled and used the module under Python 1.5, GNU g++ 2.8.0, on a UNIX machine. The script 'test.py' is provided to test the module. The expected output is contained in 'testout.txt'. To try: g++ -fpic -I$PYTHON_INCLUDE -c cxxmodule.cxx -o cxxmodule.o g++ -shared -o cxxmodule.so cxxmodule.o python test.py >& out.txt diff out.txt testout.txt I hope you enjoy the package. Any comments are very welcome. Regards, Ulli -- _____________________________________________________________ | | | Ullrich Koethe Fraunhofer Institute for Computer Graphics | | Rostock | | | | Phone: +49-381-4024-114 Joachim-Jungius-Str. 9 | | Fax: +49-381-4024-199 D - 18059 Rostock | | Email: koethe@egd.igd.fhg.de Germany | |_____________________________________________________________|
- keep the entire wrapping tool in just 3 header files (no library or code generator required) - keep the C++ code essentially independent of Python (no inheritance from PyObject or similar required)
At LLNL, we are using a home grown "bridge language" to glue the two pieces together. We too want to keep the C++ looking and acting like C++, but have the python looking and acting like python. Here's what we do in the header file (your class with some additions to show other features) class Foo { public: Foo(); Foo(int); virtual ~Foo(); virtual int foo_method(int, int); int getX(); void setX(int); }; class Bar : public Foo { public: Bar(); Bar(int,int); int getY(); void setY(int); }; class FooBar { public: FooBar(Foo&,Bar&); }; /*#!python wrapper('Foo', '#include "Foo.h"', ctor(), ctor(int), mfunc('void','foo_method(int,int)'), mfunc('void','foo_method(int,int,char*)'), attr('int','x','@->getX()','@->setX($1)',doc='an x attr'), doc='A foo is half of a foobar'), wrapper('Bar', inherit('Foo'), ctor(), ctor('int','int'), attr('int','y','@->getY()','@->setY($1)'), doc='A bar is half of a foobar'), ), wrapper('FooBar', ctor('Foo&','Bar&') ) */ We run our PYFFLE script over it to create method.cc and a foomodule.c file for the extensions.
from foo import Foo,Bar,FooBar # These are FUNCTIONS that create # the new types f = foo() print f # The default string method can be overriden <foo 0xdeadbeef> print f.x 0 f.x = 3 f.foo_method() ValueError: needs 2 or 3 arguments f.foo_method(1,2) I'm in foo_method b = Bar() b.foo_method(1,2) I'm in foo_method fb = FooBar(f,b)
----------------------------------------------- PYFFLE has other features too like building python sequence types (add a __len__ mfunc and a seq('kfsdkf','[int]', <get code>, <set code>) line to any PYFFLE wrapper), We also build and use a lot of STL containers, so we have support for vector<T> and list<T>. PYFFLE also will "build on the fly" to support type coercion. For instance, we have a class "Triplet" which is a triple of doubles. However, we don't want users to have to do things like wrapper('Take2Triples',ctor('Triplet&','Triplet&'),....)
F = Take2Triples(Triplet(1,2,3),Triplet(1,2,3))
when
F = Take2Triples((1,2,3),(1,2,3)) reads so much better!
So we have added a "contstructable" flag to PYFFLE that allows either the "correct" type to be at an argument site OR something from which the correct type can be built. This is real nice for things that take vector<int> arguments because we can arrange to allow ANY python sequence (something that supports integer __getitem__ and __len__) to be used directly
class myseq: def __init__(self,x): pass def __len__(self): return 3 def __getitem__(self,i): return 5 v = F([1,2,3,4]) w = F(range(0,100)) z = F(myseq('abc'))
instead of
v = F(vector_of_int(1,2,3,4)) w = F(vector_of_int(tuple(range(0,100))) ms = myseq('abc') z = F(vector_of_int(lambda x,i: x[i],ms,range(0,len(ms)))
Is there general interest in this? I am hoping to clean it up enough to put on the Starship Pat
participants (2)
-
Patrick Miller -
Ullrich Koethe