At first sight this might seem natural and straightforward. However, it is a fairly complex problem to establish cross-extension-module dependencies while maintaining the same ease of use Boost.Python provides for classes that are wrapped in the same extension module. To a large extent this complexity can be hidden from the author of a Boost.Python extension module, but not entirely.
import std_vector v = std_vector.double([1, 2, 3, 4]) v.push_back(5) v.size()Suppose the std_vector module is done well and reflects all C++ functions that are useful at the Python level, for all C++ built-in data types (std_vector.int, std_vector.long, etc.).
Suppose further that there is statistic module with a C++ class that has constructors or member functions that use or return a std::vector. For example:
class xy { public: xy(const std::vector<double>& x, const std::vector<double>& y) : m_x(x), m_y(y) {} const std::vector<double>& x() const { return m_x; } const std::vector<double>& y() const { return m_y; } double correlation(); private: std::vector<double> m_x; std::vector<double> m_y; }What is more natural than reusing the std_vector extension module to expose these constructors or functions to Python?
Unfortunately, what seems natural needs a little work in both the std_vector and the statistics module.
In the std_vector extension module, std::vector<double> is exposed to Python in the usual way with the class_builder<> template. To also enable the automatic conversion of std::vector<double> function arguments or return values in other Boost.Python C++ modules, the converters that convert a std::vector<double> C++ object to a Python object and vice versa (i.e. the to_python() and from_python() template functions) have to be exported. For example:
#include <boost/python/cross_module.hpp> //... class_builder<std::vector<double> > v_double(std_vector_module, "double"); export_converters(v_double);In the extension module that wraps class xy we can now import these converters with the import_converters<> template. For example:
#include <boost/python/cross_module.hpp> //... import_converters<std::vector<double> > v_double_converters("std_vector", "double");That is all. All the attributes that are defined for std_vector.double in the std_vector Boost.Python module will be available for the returned objects of xy.x() and xy.y(). Similarly, the constructor for xy will accept objects that were created by the std_vectormodule.
class_builder<store> py_store(your_module, "store"); export_converters_noncopyable(py_store);The corresponding import_converters<> statement does not need any special attention:
import_converters<store> py_store("noncopyable_export", "store");
import std_vector import statistics x = std_vector.double([1, 2, 3, 4]) y = std_vector.double([2, 4, 6, 8]) xy = statistics.xy(x, y) xy.correlation()In this example it is clear that Python has to be able to find both the std_vector and the statistics extension module. In other words, both extension modules need to be in the Python module search path (sys.path).
The situation is not always this obvious. Suppose the statistics module has a random() function that returns a vector of random numbers with a given length:
import statistics x = statistics.random(5) y = statistics.random(5) xy = statistics.xy(x, y) xy.correlation()A naive user will not easily anticipate that the std_vector module is used to pass the x and y vectors around. If the std_vector module is in the Python module search path, this form of ignorance is of no harm. On the contrary, we are glad that we do not have to bother the user with details like this.
If the std_vector module is not in the Python module search path, a Python exception will be raised:
Traceback (innermost last): File "foo.py", line 2, in ? x = statistics.random(5) ImportError: No module named std_vectorAs is the case with any system of a non-trivial complexity, it is important that the setup is consistent and complete.
Suppose there is a module ivect that implements vectors of integers, and a similar module dvect that implements vectors of doubles. We want to be able do convert an integer vector to a double vector and vice versa. For example:
import ivect iv = ivect.ivect((1,2,3,4,5)) dv = iv.as_dvect()The last expression will implicitly import the dvect module in order to enable the conversion of the C++ representation of dvect to a Python object. The analogous is possible for a dvect:
import dvect dv = dvect.dvect((1,2,3,4,5)) iv = dv.as_ivect()Now the ivect module is imported implicitly.
Note that the two-way dependencies are possible because the dependencies are resolved only when needed. This is, the initialization of the ivect module does not rely on the dvect module, and vice versa. Only if as_dvect() or as_ivect() is actually invoked will the corresponding module be implicitly imported. This also means that, for example, the dvect module does not have to be available at all if as_dvect() is never used.
If a library is wrapped that consists of both header files and compiled components (e.g. libdvect.a, dvect.lib, etc.), both the Boost.Python extension module with the export_converters() statement and the module with the import_converters<> statement need to be linked against the object library. Ideally one would build a shared library (e.g. libdvect.so, dvect.dll, etc.). However, this introduces the issue of having to configure the search path for the dynamic loading correctly. For small libraries it is therefore often more convenient to ignore the fact that the object files are loaded into memory more than once.
Another motivation for the cross-module support is that two extension modules that wrap the same class cannot both be imported into Python. For example, if there are two modules A and B that both wrap a given class X, this will work:
import A x = A.X()This will also work:
import B x = B.X()However, this will fail:
import A import B python: /net/cci/rwgk/boost/boost/python/detail/extension_class.hpp:866: static void boost::python::detail::class_registry<X>::register_class(boost::python::detail::extension_class_base *): Assertion `static_class_object == 0' failed. AbortA good solution is to wrap class X only once. Depending on the situation, this could be done by module A or B, or an additional small extension module that only wraps and exports class X.
Finally, there can be important psychological or political reasons for using the cross-module support. If a group of classes is lumped together with many others in a huge module, the authors will have difficulties in being identified with their work. The situation is much more transparent if the work is represented by a module with a recognizable name. This is not just a question of strong egos, but also of getting credit and funding.
Updated: April 2001