diff --git a/doc/cross_module.html b/doc/cross_module.html new file mode 100644 index 00000000..08c39bfe --- /dev/null +++ b/doc/cross_module.html @@ -0,0 +1,336 @@ + + +
+
++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_vector ++ +As 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. +Abort ++ +A 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 + +