Exposing containers

This tutorial shows how you can expose an STL-container class to Python. The lass::python infrastructure provides default place-holders for the most common STL-containers so that you don't need to manually create shadow classes for concrete template container classes. std::complex, std::vector, std::list, std::map and their iterators are by default supported. The template argument does not matter, as long as it is known to lass::python you can use in a container. This means that you can also expose arbitrarily nested containers without effort. To make this all work seaminglessly the lass::python equivalents of the std::vector and others have to be introduced in Python. In concreto this means that any std::vector that you expose to Python will be seen in Python as a custom lass::python type and not the native Python list. This comes with the advantage that some functionality can be added to those special types (like read-onlyness) but has the disadvantage that Python routines expecting native Python lists can possibly use (yet) unsupported functionality. There is however a simple conversion possible between the lass::python container classes and the native Python containers like lists, tuples and dictionaries.

This tutorial contains following paragraphs:

We propose again the same simple class hierarchy to demonstrate the concepts: Foo, Bar and FooBar. The C++ code is in no means intended to demonstrate proper class hierarchy but is intended to show in as short as possible example the power of rapidly exporting containers to Python. The new code is highlighted in blue. So in general, when you have the choice of course, you don't want to juggle around with naked pointers.
class Foo
{
public:
Foo() {}
virtual ~Foo() {}
virtual int overloaded(int iA) { return iA+1;}
int fooSpecific() { return -1; }
};

class Bar : public Foo
{
public:
Bar() {}
virtual ~Bar() {}
virtual int overloaded(int iA) { return iA+2;}
int barSpecific() { return -2; }
};

class FooBarred : public Bar
{
public:
FooBarred() {}
virtual ~FooBarred() {}
virtual int overloaded(int iA) { return iA+3;}
int fooBarSpecific() { return -3; }
};

Foo* factoryObject(int iWhich)
{
if (iWhich==3)
return new FooBarred();
if (iWhich==2)
return new Bar();
return new Foo();
}

struct Containers
{
static std::vector aListOfFoos()
{
std::vector temp;
temp.push_back(factoryObject(1));
temp.push_back(factoryObject(2));
temp.push_back(factoryObject(3));
return temp;
}
static const std::vector& aConstListOfFoos()
{
static std::vector temp;
if (temp.size()==0)
{
temp.push_back(factoryObject(1));
temp.push_back(factoryObject(2));
temp.push_back(factoryObject(3));
}
return temp;
}
static std::map aMapOfFoos()
{
std::map temp;
temp.insert(std::pair(1,factoryObject(1)));
temp.insert(std::pair(2,factoryObject(2)));
temp.insert(std::pair(3,factoryObject(3)));
return temp;
}
static int doSum(const std::vector& iVector)
{
return std::accumulate(iVector.begin(),iVector.end(),0);
}
};


PY_SHADOW_CLASS( LASS_DLL_EXPORT, PyFoo, Foo )
PY_DECLARE_CLASS_NAME( PyFoo, "Foo" )
PY_SHADOW_DOWN_CASTERS( PyFoo )
PY_CLASS_METHOD( PyFoo, overloaded )
PY_CLASS_METHOD( PyFoo, fooSpecific )

PY_SHADOW_CLASS_DERIVED( LASS_DLL_EXPORT, PyBar, Bar, PyFoo)
PY_DECLARE_CLASS_NAME( PyBar, "Bar" )
PY_SHADOW_DOWN_CASTERS( PyBar )
PY_CLASS_METHOD( PyBar, barSpecific )

PY_SHADOW_CLASS_DERIVED( LASS_DLL_EXPORT, PyFooBarred, FooBarred, PyBar)
PY_DECLARE_CLASS_NAME( PyFooBarred, "FooBarred" )
PY_SHADOW_DOWN_CASTERS( PyFooBarred )
PY_CLASS_METHOD( PyBar, fooBarSpecific )

PY_DECLARE_MODULE( embedding )
PY_MODULE_CLASS( embedding, PyFoo, "")
PY_MODULE_CLASS( embedding, PyBar, "")
PY_MODULE_CLASS( embedding, PyFooBarred, "")
PY_MODULE_FUNCTION( embedding, factoryObject )

PY_MODULE_FUNCTION_NAME( embedding, Containers::aListOfFoos, "aListOfFoos" )
PY_MODULE_FUNCTION_NAME( embedding, Containers::aConstListOfFoos, "aConstListOfFoos" )
PY_MODULE_FUNCTION_NAME( embedding, Containers::aMapOfFoos, "aMapOfFoos" )
PY_MODULE_FUNCTION_NAME( embedding, Containers::doSum, "doSum" )

PY_EXTENSION_MODULE( embedding )

Exporting

PY_MODULE_FUNCTION_NAME( embedding, Containers::aListOfFoos, "aListOfFoos" )
PY_MODULE_FUNCTION_NAME( embedding, Containers::aConstListOfFoos, "aConstListOfFoos" )
PY_MODULE_FUNCTION_NAME( embedding, Containers::aMapOfFoos, "aMapOfFoos" )
Nothing special is happening here, the container return values are treated just like any other return value of a function that will be exported.
PY_MODULE_FUNCTION_NAME( embedding, Containers::doSum, "doSum" )
Again nothing that you need to worry about when writing down the macros. What has not been discussed in the introduction is on how native Python objects will brought into C++ again. In lass::python we have taken a pragmatic approach and try to mimic the Python behavior as much as possible. When an object is presented to the doSum routine it will be checked whether it is some Python type that can be translated into a std::vector like structure. For std::vector this typically means that lists and tuples will be converted. The actual precondition is that the Python object presented supports the sequence interface. So writing in Python embedding.doSum([1,3,7]) will work just fine. Writing embedding.doSum([1,complex(3,5)]) won't work because the elements in the list are not of the same type. Something which is not supported by a std::vector. Ok, before you start metioning boost::variant or whatever::variant, lass::python can support such things but just demands that your argument has a conversion operator from Python to C++. How to handle such a situation will be covered in the NSFAQ.