diff --git a/doc/tutorial/doc/basic_interface.html b/doc/tutorial/doc/basic_interface.html new file mode 100644 index 00000000..cb4a28f4 --- /dev/null +++ b/doc/tutorial/doc/basic_interface.html @@ -0,0 +1,77 @@ + +
+ +
+ |
+ + Basic Interface + | +
![]() |
+ ![]() |
+ ![]() |
+
+Class object wraps PyObject*. All the intricacies of dealing with +PyObjects such as managing reference counting are handled by the +object class. C++ object interoperability is seamless. Boost.Python C++ +objects can in fact be explicitly constructed from any C++ object.
++To illustrate, this Python code snippet:
+
+ def f(x, f):
+ if (y == 'foo'):
+ x[3:7] = 'bar'
+ else:
+ x.items += f(3, x)
+ return x
+
+ def getfunc():
+ return f;
+
++Can be rewritten in C++ using Boost.Python facilities this way:
+
+ object f(object x, object f) {
+ if (f == "foo")
+ x.slice(3,7) = "bar";
+ else
+ x.attr("items") += f(3, x);
+ return x;
+ }
+ object getfunc() {
+ return object(f);
+ }
+
++Apart from cosmetic differences due to the fact that we are writing the +code in C++, the look and feel should be immediately apparent to the Python +coder.
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ Building + an Extension Module | +
![]() |
+ ![]() |
+ ![]() |
+
Every Boost.Python extension module must be linked with the boost_python shared + library. To build boost_python, use Boost.Build + in the usual way from the libs/python/build subdirectory of your boost + installation (if you have already built boost from the top level this may have + no effect, since the work is already done).
+You may need to configure the following variables to point Boost.Build at your + Python installation:
+| Variable Name | +Semantics | +Default | +Notes | +
| PYTHON_ROOT | +The root directory of your Python installation | +Windows: + c:/tools/python + Unix: /usr/local |
+ On Unix, this is the --with-prefix= directory used to configure + Python | +
| PYTHON_VERSION | +The The 2-part python Major.Minor version number | +Windows: 2.1 Unix: 1.5 | +Be sure not to include a third number, e.g. not "2.2.1", even + if that's the version you have. | +
| PYTHON_INCLUDES | +path to Python #include directories | +Autoconfigured from + PYTHON_ROOT |
+ + |
| PYTHON_LIB_PATH | +path to Python library object. | +Autoconfigured from + PYTHON_ROOT |
+ + |
| PYTHON_STDLIB_PATH | +path to Python standard library modules | +Autoconfigured from + PYTHON_ROOT |
+ + |
| CYGWIN_ROOT | +path to the user's Cygwin installation | +Autoconfigured from + PYTHON_ROOT |
+ Cygwin only. This and the following + two settings are useful when building with multiple toolsets on Windows, + since Cygwin requires a different build of Python. | +
| GCC_PYTHON_ROOT | +path to the user's Cygwin Python installation | +$(CYGWIN_ROOT) + /usr/local |
+ Cygwin only | +
| GCC_DEBUG_PYTHON_ROOT | +path to the user's Cygwin pydebug + build | +$(CYGWIN_ROOT) + /usr/local/pydebug |
+ Cygwin only | +
The build process will create a libs/python/build/bin-stage subdirectory + of the boost root (or of $(ALL_LOCATE_TARGET), if you have set that + variable), containing the built libraries. The libraries are actually built + to unique directories for each toolset and variant elsewhere in the filesystem, + and copied to the bin-stage directory as a convenience, so if you build with + multiple toolsets at once, the product of later toolsets will overwrite that + of earlier toolsets in bin-stage.
+To build and test Boost.Python from within the libs/python/build directory, + invoke
+bjam -sTOOLS=toolset test+
This will update all of the Boost.Python v1 test and example targets. The tests + are relatively quiet by default. To get more-verbose output, you might try
+bjam -sTOOLS=toolset -sPYTHON_TEST_ARGS=-v test+
which will print each test's Python code with the expected output as it passes.
+Though there are other approaches, the easiest way to build an extension module + using Boost.Python is with Boost.Build. Until Boost.Build v2 is released, cross-project + build dependencies are not supported, so it works most smoothly if you add a + new subproject to your boost installation. The libs/python/example + subdirectory of your boost installation contains a minimal example (along with + many extra sources). To copy the example subproject:
+If you can't modify or copy your boost installation, the alternative is to + create your own Boost.Build project. A similar example you can use as a starting + point is available in this archive. You'll + need to edit the Jamfile and Jamrules files, depending on the relative location + of your Boost installation and the new project. Note that automatic testing + of extension modules is not available in this configuration.
+Three variant configurations of all python-related targets are supported, and + can be selected by setting the BUILD variable:
+ * release (optimization, -DNDEBUG)
+ * debug (no optimization -D_DEBUG)
+ * debug-python (no optimization, -D_DEBUG -DBOOST_DEBUG_PYTHON)
The first two variants of the boost_python library are built by default, and + are compatible with the default Python distribution. The debug-python variant + corresponds to a specially-built debugging version of Python. On Unix platforms, + this python is built by adding --with-pydebug when configuring the + Python build. On Windows, the debugging version of Python is generated by the + "Win32 Debug" target of the PCBuild.dsw Visual C++ 6.0 project in + the PCBuild subdirectory of your Python distribution. Extension modules built + with Python debugging enabled are not link-compatible with a non-debug build + of Python. Since few people actually have a debug build of Python (it doesn't + come with the standard distribution), the normal debug variant builds modules + which are compatible with ordinary Python.
+On many windows compilers, when extension modules are built with -D_DEBUG, + Python defaults to force linking with a special debugging version of the Python + DLL. Since this debug DLL isn't supplied with the default Python installation + for Windows, Boost.Python uses boost/python/detail/wrap_python.hpp + to temporarily undefine _DEBUG when Python.h is #included + - unless BOOST_DEBUG_PYTHON is defined.
+If you want the extra runtime checks available with the debugging version of + the library, #define BOOST_DEBUG_PYTHON + to re-enable python debuggin, and link with the debug-python variant of boost_python.
+If you do not #define BOOST_DEBUG_PYTHON, + be sure that any source files in your extension module #include + <boost/python/detail/wrap_python.hpp> instead of the usual Python.h, + or you will have link incompatibilities.
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Building Hello World + | +
![]() |
+ ![]() |
+ ![]() |
+
+Now the first thing you'd want to do is to build the Hello World module and +try it for yourself in Python. In this section, we shall outline the steps +necessary to achieve that. We shall use the build tool that comes bundled +with every boost distribution: bjam.
++We shall skip over the details. Our objective will be to simply create the +hello world module and run it in Python. For a complete reference to +building Boost.Python, check out: +building.html. +After this brief bjam tutorial, we should have built two DLLs:
++if you are on Windows, and
++if you are on Unix.
++The tutorial example can be found in the directory: +libs/python/example/tutorial. There, you can find:
++The hello.cpp file is our C++ hello world example. The Jamfile is a +minimalist bjam script that builds the DLLs for us.
++Before anything else, you should have the bjam executable in your boost +directory. Pre-built Boost.Jam executables are available for most +platforms. For example, a pre-built Microsoft Windows bjam executable can +be downloaded +here. +The complete list of bjam pre-built executables can be found +here.
+
+
+Here is our minimalist Jamfile:
+
+ subproject libs/python/example/tutorial ;
+
+ SEARCH on python.jam = $(BOOST_BUILD_PATH) ;
+ include python.jam ;
+
+ extension hello # Declare a Python extension called hello
+ : hello.cpp # source
+ <dll>../../build/boost_python # dependencies
+ ;
+
+First, we need to specify our location in the boost project hierarchy. +It so happens that the tutorial example is located in /libs/python/example/tutorial. +Thus:
+
+ subproject libs/python/example/tutorial ;
+
+Then we will include the definitions needed by Python modules:
+
+ SEARCH on python.jam = $(BOOST_BUILD_PATH) ;
+ include python.jam ;
+
+Finally we declare our hello extension:
+
+ extension hello # Declare a Python extension called hello
+ : hello.cpp # source
+ <dll>../../build/boost_python # dependencies
+ ;
+
+bjam is run using your operating system's command line interpreter.
+Start it up.
+Make sure that the environment is set so that we can invoke the C++ +compiler. With MSVC, that would mean running the Vcvars32.bat batch +file. For instance:
+
+ C:\Program Files\Microsoft Visual Studio\VC98\bin\Vcvars32.bat
+
++Some environment variables will have to be setup for proper building of our +Python modules. Example:
+
+ set PYTHON_ROOT=c:/dev/tools/python
+ set PYTHON_VERSION=2.2
+
+
+The above assumes that the Python installation is in c:/dev/tools/python
+and that we are using Python version 2.2. You'll have to tweak this path
+appropriately.
Be sure not to include a third number, e.g. not "2.2.1",
+even if that's the version you have.
+Now we are ready... Be sure to cd to libs/python/example/tutorial +where the tutorial "hello.cpp" and the "Jamfile" is situated.
++Finally:
+
+ bjam -sTOOLS=msvc
+
++We are again assuming that we are using Microsoft Visual C++ version 6. If +not, then you will have to specify the appropriate tool. See + +Building Boost Libraries for +further details.
++It should be building now:
+
+ cd C:\dev\boost\libs\python\example\tutorial
+ bjam -sTOOLS=msvc
+ ...patience...
+ ...found 1703 targets...
+ ...updating 40 targets...
+
+And so on... Finally:
+
+ vc-C++ ..\..\..\..\libs\python\example\tutorial\bin\hello.pyd\msvc\debug\
+ runtime-link-dynamic\hello.obj
+ hello.cpp
+ vc-Link ..\..\..\..\libs\python\example\tutorial\bin\hello.pyd\msvc\debug\
+ runtime-link-dynamic\hello.pyd ..\..\..\..\libs\python\example\tutorial\bin\
+ hello.pyd\msvc\debug\runtime-link-dynamic\hello.lib
+ Creating library ..\..\..\..\libs\python\example\tutorial\bin\hello.pyd\
+ msvc\debug\runtime-link-dynamic\hello.lib and object ..\..\..\..\libs\python\
+ example\tutorial\bin\hello.pyd\msvc\debug\runtime-link-dynamic\hello.exp
+ ...updated 40 targets...
+
+If all is well, you should now have:
++if you are on Windows, and
++if you are on Unix.
++boost_python.dll can be found somewhere in libs\python\build\bin +while hello.pyd can be found somewhere in +libs\python\example\tutorial\bin. After a successful build, you can just +link in these DLLs with the Python interpreter. In Windows for example, you +can simply put these libraries inside the directory where the Python +executable is.
++You may now fire up Python and run our hello module:
+
+ >>> import hello
+ >>> print hello.greet()
+ hello, world
+
+There you go... Have fun!
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Call Policies + | +
![]() |
+ ![]() |
+ ![]() |
+
+In C++, we often deal with arguments and return types such as pointers +and references. Such primitive types are rather, ummmm, low level and +they really don't tell us much. At the very least, we don't know the +owner of the pointer or the referenced object. No wonder languages +such as Java and Python never deal with such low level entities. In +C++, it's usually considered a good practice to use smart pointers +which exactly describe ownership semantics. Still, even good C++ +interfaces use raw references and pointers sometimes, so Boost.Python +must deal with them. To do this, it may need your help. Consider the +following C++ function:
+
+ X& f(Y& y, Z* z);
+
++How should the library wrap this function? A naive approach builds a +Python X object around result reference. This strategy might or might +not work out. Here's an example where it didn't
+
+ >>> x = f(y, z) #x refers to some C++ X
+ >>> del y
+ >>> x.some_method() #CRASH!
+
++What's the problem?
++Well, what if f() was implemented as shown below:
+
+ X& f(Y& y, Z* z)
+ {
+ y.z = z;
+ return y.x;
+ }
+
++The problem is that the lifetime of result X& is tied to the lifetime +of y, because the f() returns a reference to a member of the y +object. This idiom is is not uncommon and perfectly acceptable in the +context of C++. However, Python users should not be able to crash the +system just by using our C++ interface. In this case deleting y will +invalidate the reference to X. We have a dangling reference.
++Here's what's happening:
++We could copy result into a new object:
+
+ >>> f(y, z).set(42) #Result disappears
+ >>> y.x.get() #No crash, but still bad
+ 3.14
+
++This is not really our intent of our C++ interface. We've broken our +promise that the Python interface should reflect the C++ interface as +closely as possible.
++Our problems do not end there. Suppose Y is implemented as follows:
+
+ struct Y
+ {
+ X x; Z* z;
+ int z_value() { return z->value(); }
+ };
+
++Notice that the data member z is held by class Y using a raw +pointer. Now we have a potential dangling pointer problem inside Y:
+
+ >>> x = f(y, z) #y refers to z
+ >>> del z #Kill the z object
+ >>> y.z_value() #CRASH!
+
++For reference, here's the implementation of f again:
+
+ X& f(Y& y, Z* z)
+ {
+ y.z = z;
+ return y.x;
+ }
+
++Here's what's happening:
++Call Policies may be used in situations such as the example detailed above. +In our example, return_internal_reference and with_custodian_and_ward +are our friends:
+
+ def("f", f,
+ return_internal_reference<1,
+ with_custodian_and_ward<1, 2> >());
+
++What are the 1 and 2 parameters, you ask?
+
+ return_internal_reference<1
+
++Informs Boost.Python that the first argument, in our case Y& y, is the +owner of the returned reference: X&. The "1" simply specifies the +first argument. In short: "return an internal reference X& owned by the +1st argument Y& y".
+
+ with_custodian_and_ward<1, 2>
+
++Informs Boost.Python that the lifetime of the argument indicated by ward +(i.e. the 2nd argument: Z* z) is dependent on the lifetime of the +argument indicated by custodian (i.e. the 1st argument: Z* z).
++It is also important to note that we have defined two policies above. Two +or more policies can be composed by chaining. Here's the general syntax:
+
+ policy1<args...,
+ policy2<args...,
+ policy3<args...> > >
+
++Here is the list of predefined call policies. A complete reference detailing +these can be found +here.
+
+ Remember the Zen, Luke:+"Explicit is better than implicit" +"In the face of ambiguity, refuse the temptation to guess" |
+
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Class Data Members + | +
![]() |
+ ![]() |
+ ![]() |
+
+Data members may also be exposed to Python so that they can be +accessed as attributes of the corresponding Python class. Each data +member that we wish to be exposed may be regarded as read-only or +read-write. Consider this class Var:
+
+ struct Var
+ {
+ Var(std::string name) : name(name), value() {}
+ std::string const name;
+ float value;
+ };
+
++Our C++ Var class and its data members can be exposed to Python:
+
+ class_<Var>("Var", init<std::string>())
+ .def_readonly("name", &Var::name)
+ .def_readwrite("value", &Var::value);
+
++Then, in Python:
+
+ >>> x = Var('pi')
+ >>> x.value = 3.14
+ >>> print x.name, 'is around', x.value
+ pi is around 3.14
+
++Note that name is exposed as read-only while value is exposed +as read-write.
+
+ >>> x.name = 'e' # can't change name
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in ?
+ AttributeError: can't set attribute
+
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Class Operators/Special Functions + | +
![]() |
+ ![]() |
+ ![]() |
+
+C is well known for the abundance of oparators. C++ extends this to the +extremes by allowing operator overloading. Boost.Python takes advantage of +this and makes it easy to wrap C++ operator-powered classes.
++Consider a file position class FilePos and a set of operators that take +on FilePos instances:
+
+ class FilePos { /*...*/ };
+
+ FilePos operator+(FilePos, int);
+ FilePos operator+(int, FilePos);
+ int operator-(FilePos, FilePos);
+ FilePos operator-(FilePos, int);
+ FilePos& operator+=(FilePos&, int);
+ FilePos& operator-=(FilePos&, int);
+ bool operator<(FilePos, FilePos);
+
++The class and the various operators can be mapped to Python rather easily +and intuitively:
+
+ class_<FilePos>("FilePos")
+ .def(self + int()) // __add__
+ .def(int() + self) // __radd__
+ .def(self - self) // __sub__
+ .def(self - int()) // __rsub__
+ .def(self += int()) // __iadd__
+ .def(self -= other<int>())
+ .def(self < self); // __lt__
+
++The code snippet above is very clear and needs almost no explanation at +all. It is virtually the same as the operators' signatures. Just take +note that self refers to FilePos object. Also, not every class T that +you might need to interact with in an operator expression is (cheaply) +default-constructible. You can use other<T>() in place of an actual +T instance when writing "self expressions".
++Python has a few more Special Methods. Boost.Python supports all of the +standard special method names supported by real Python class instances. A +similar set of intuitive interfaces can also be used to wrap C++ functions +that correspond to these Python special functions. Example:
+
+ class Rational
+ { operator double() const; };
+
+ Rational pow(Rational, Rational);
+ Rational abs(Rational);
+ ostream& operator<<(ostream&,Rational);
+
+ class_<Rational>()
+ .def(float_(self)) // __float__
+ .def(pow(self)) // __pow__
+ .def(abs(self)) // __abs__
+ .def(str(self)) // __str__
+ ;
+
++Need we say more?
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Class Properties + | +
![]() |
+ ![]() |
+ ![]() |
+
+In C++, classes with public data members are usually frowned +upon. Well designed classes that take advantage of encapsulation hide +the class' data members. The only way to access the class' data is +through access (getter/setter) functions. Access functions expose class +properties. Here's an example:
+
+ struct Num
+ {
+ Num();
+ float get() const;
+ void set(float value);
+ ...
+ };
+
++However, in Python attribute access is fine; it doesn't neccessarily break +encapsulation to let users handle attributes directly, because the +attributes can just be a different syntax for a method call. Wrapping our +Num class using Boost.Python:
+
+ class_<Num>("Num")
+ .add_property("rovalue", &Var::get)
+ .add_property("value", &Var::get, &Var::set);
+
++And at last, in Python:
+
+ >>> x = Num()
+ >>> x.value = 3.14
+ >>> x.value, x.rovalue
+ (3.14, 3.14)
+ >>> x.rovalue = 2.17 #error!
+
++Take note that the class property rovalue is exposed as read-only +since the rovalue setter member function is not passed in:
+
+ .add_property("rovalue", &Var::get)
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Class Virtual Functions + | +
![]() |
+ ![]() |
+ ![]() |
+
+In this section, we shall learn how to make functions behave +polymorphically through virtual functions. Continuing our example, let us +add a virtual function to our Base class:
+
+ struct Base
+ {
+ virtual int f() = 0;
+ };
+
++Since f is a pure virtual function, Base is now an abstract +class. Given an instance of our class, the free function call_f +calls some implementation of this virtual function in a concrete +derived class:
+
+ int call_f(Base& b) { return b.f(); }
+
++To allow this function to be implemented in a Python derived class, we +need to create a class wrapper:
+
+ struct BaseWrap : Base
+ {
+ BaseWrap(PyObject* self_)
+ : self(self_) {}
+ int f() { return call_method<int>(self, "f"); }
+ PyObject* self;
+ };
+
+
+ member function and methodsPython, like +many object oriented languages uses the term methods. Methods +correspond roughly to C++'s member functions |
+
+Our class wrapper BaseWrap is derived from Base. Its overridden +virtual member function f in effect calls the corresponding method +of the Python object self, which is a pointer back to the Python +Base object holding our BaseWrap instance.
+
+ Why do we need BaseWrap?+ +You may ask, "Why do we need the BaseWrap derived class? This could +have been designed so that everything gets done right inside of +Base." + +One of the goals of Boost.Python is to be minimally intrusive on an +existing C++ design. In principle, it should be possible to expose the +interface for a 3rd party library without changing it. To unintrusively +hook into the virtual functions so that a Python override may be called, we +must use a derived class. + +Note however that you don't need to do this to get methods overridden +in Python to behave virtually when called from Python. The only +time you need to do the BaseWrap dance is when you have a virtual +function that's going to be overridden in Python and called +polymorphically from C++. |
+
+Wrapping Base and the free function call_f:
+
+ class_<Base, BaseWrap, boost::noncopyable>("Base", no_init)
+ ;
+ def("call_f", call_f);
+
++Notice that we parameterized the class_ template with BaseWrap as the +second parameter. What is noncopyable? Without it, the library will try +to create code for converting Base return values of wrapped functions to +Python. To do that, it needs Base's copy constructor... which isn't +available, since Base is an abstract class.
++In Python, let us try to instantiate our Base class:
+
+ >>> base = Base()
+ AttributeError: ...
+
++Why is it an error? Base is an abstract class. As such it is advisable +to define the Python wrapper with no_init as we have done above. Doing +so will disallow abstract base classes such as Base to be instantiated.
++Now, at last, we can even derive from our base class Base in Python:
+
+ >>> class Derived(Base):
+ ... def f(self):
+ ... return 42
+ ...
+
++Cool eh? A Python class deriving from a C++ class!
++Let's now make an instance of our Python class Derived:
+
+ >>> derived = Derived()
+
++Calling derived.f():
+
+ >>> derived.f()
+ 42
+
++Will yield the expected result. Finally, calling calling the free function +call_f with derived as argument:
+
+ >>> call_f(derived())
+ 42
+
++Will also yield the expected result.
++Here's what's happening:
++Rewind back to our Base class, if its member function f was not +declared as pure virtual:
+
+ struct Base
+ {
+ virtual int f() { return 0; }
+ };
+
++And instead is implemented to return 0, as shown above.
+
+ struct BaseWrap : Base
+ {
+ BaseWrap(PyObject* self_)
+ : self(self_) {}
+ int f() { return call_method<int>(self, "f"); }
+ static int default_f(Base* b) { return b->Base::f(); } // <<=== added
+ PyObject* self;
+ };
+
++then, our Boost.Python wrapper:
+
+ class_<Base, BaseWrap>("Base")
+ .def("f", &BaseWrap::default_f)
+ ;
+
++Note that we are allowing Base objects to be instantiated this time, +unlike before where we specifically defined the class_<Base> with +no_init.
++In Python, the results would be as expected:
+
+ >>> base = Base()
+ >>> class Derived(Base):
+ ... def f(self):
+ ... return 42
+ ...
+ >>> derived = Derived()
+
++Calling base.f():
+
+ >>> base.f()
+ 0
+
++Calling derived.f():
+
+ >>> derived.f()
+ 42
+
++Calling call_f, passing in a base object:
+
+ >>> call_f(base)
+ 0
+
++Calling call_f, passing in a derived object:
+
+ >>> call_f(derived())
+ 42
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Constructors + | +
![]() |
+ ![]() |
+ ![]() |
+
+Our previous example didn't have any explicit constructors. +Since World is declared as a plain struct, it has an implicit default +constructor. Boost.Python exposes the default constructor by default, +which is why we were able to write
+
+ >>> planet = hello.World()
+
++We may wish to wrap a class with a non-default constructor. Let us +build on our previous example:
+
+ struct World
+ {
+ World(std::string msg): msg(msg) {} // added constructor
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string msg;
+ };
+
++This time World has no default constructor; our previous +wrapping code would fail to compile when the library tried to expose +it. We have to tell class_<World> about the constructor we want to +expose instead.
+
+ #include <boost/python.hpp>
+ using namespace boost::python;
+
+ BOOST_PYTHON_MODULE(hello)
+ {
+ class_<World>("World", init<std::string>())
+ .def("greet", &World::greet)
+ .def("set", &World::set)
+ ;
+ }
+
++init<std::string>() exposes the constructor taking in a +std::string (in Python, constructors are spelled +""__init__"").
++We can expose additional constructors by passing more init<...>s to +the def() member function. Say for example we have another World +constructor taking in two doubles:
+
+ class_<World>("World", init<std::string>())
+ .def(init<double, double>())
+ .def("greet", &World::greet)
+ .def("set", &World::set)
+ ;
+
++On the other hand, if we do not wish to expose any constructors at +all, we may use no_init instead:
+
+ class_<Abstract>("Abstract", no_init)
+
++This actually adds an __init__ method which always raises a +Python RuntimeError exception.
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Default Arguments + | +
![]() |
+ ![]() |
+ ![]() |
+
+Boost.Python wraps (member) function pointers. Unfortunately, C++ function +pointers carry no default argument info. Take a function f with default +arguments:
+
+ int f(int, double = 3.14, char const* = "hello");
+
++But the type of a pointer to the function f has no information +about its default arguments:
+
+ int(*g)(int,double,char const*) = f; // defaults lost!
+
++When we pass this function pointer to the def function, there is no way +to retrieve the default arguments:
+
+ def("f", f); // defaults lost!
+
++Because of this, when wrapping C++ code in earlier versions of +Boost.Python, we had to resort to writing thin wrappers:
+
+ // write "thin wrappers"
+ int f1(int x) { f(x); }
+ int f2(int x, double y) { f(x,y); }
+
+ /*...*/
+
+ // in module init
+ def("f", f); // all arguments
+ def("f", f2); // two arguments
+ def("f", f1); // one argument
+
++When you want to wrap functions (or member functions) that either:
++Boost.Python now has a way to make it easier.
++For instance, given a function:
+
+ int foo(int a, char b = 1, unsigned c = 2, double d = 3);
+
++The macro invocation:
+
+ BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 1, 4)
+
++Will automatically create the thin wrappers for us. This macro will create +a class foo_overloads that can be passed on to def(...). The third +and fourth macro argument are the minimum arguments and maximum arguments, +respectively. In our foo function the minimum number of arguments is 1 +and the maximum number of arguments is 4. The def(...) function will +automatically add all the foo variants for us:
+
+ .def("foo", foo, foo_overloads());
+
++A similar facility is provided for class constructors, again, with +default arguments or a sequence of overloads. Remember init<...>? For example, +given a class X with a constructor:
+
+ struct X
+ {
+ X(int a, char b = 'D', std::string c = "constructor", double d = 0.0);
+ /*...*/
+ }
+
++You can easily add this constructor to Boost.Python in one shot:
+
+ .def(init<int, optional<char, std::string, double> >())
+
++Notice the use of init<...> and optional<...> to signify the default +(optional arguments).
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Derived Object types + | +
![]() |
+ ![]() |
+ ![]() |
+
+Boost.Python comes with a set of derived object types corresponding to +that of Python's:
++These derived object types act like real Python types. For instance:
+
+ str(1) ==> "1"
+
++Wherever appropriate, a particular derived object has corresponding +Python type's methods. For instance, dict has a keys() method:
+
+ d.keys()
+
++make_tuple is provided for declaring tuple literals. Example:
+
+ make_tuple(123, 'D', "Hello, World", 0.0);
+
++In C++, when Boost.Python objects are used as arguments to functions, +subtype matching is required. For example, when a function f, as +declared below, is wrapped, it will only accept instances of Python's +str type and subtypes.
+
+ void f(str name)
+ {
+ object n2 = name.attr("upper")(); // NAME = name.upper()
+ str NAME = name.upper(); // better
+ object msg = "%s is bigger than %s" % make_tuple(NAME,name);
+ }
+
++In finer detail:
+
+ str NAME = name.upper();
+
++Illustrates that we provide versions of the str type's methods as C++ +member functions.
+
+ object msg = "%s is bigger than %s" % make_tuple(NAME,name);
+
++Demonstrates that you can write the C++ equivalent of "format" % x,y,z +in Python, which is useful since there's no easy way to do that in std C++.
+
+
Beware the common pitfall of forgetting that the constructors
+of most of Python's mutable types make copies, just as in Python.
+Python:
+
+ >>> d = dict(x.__dict__) #copies x.__dict__
+ >>> d['whatever'] #modifies the copy
+
++C++:
+
+ dict d(x.attr("__dict__")); #copies x.__dict__
+ d['whatever'] = 3; #modifies the copy
+
++Due to the dynamic nature of Boost.Python objects, any class_<T> may +also be one of these types! The following code snippet wraps the class +(type) object.
++We can use this to create wrapped instances. Example:
+
+ object vec345 = (
+ class_<Vec2>("Vec2", init<double, double>())
+ .def_readonly("length", &Point::length)
+ .def_readonly("angle", &Point::angle)
+ )(3.0, 4.0);
+
+ assert(vec345.attr("length") == 5.0);
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Exception Translation + | +
![]() |
+ ![]() |
+ ![]() |
+
+All C++ exceptions must be caught at the boundary with Python code. This +boundary is the point where C++ meets Python. Boost.Python provides a +default exception handler that translates selected standard exceptions, +then gives up:
+
+ raise RuntimeError, 'unidentifiable C++ Exception'
+
++Users may provide custom translation. Here's an example:
+
+ struct PodBayDoorException;
+ void translator(PodBayDoorException& x) {
+ PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave...");
+ }
+ BOOST_PYTHON_MODULE(kubrick) {
+ register_exception_translator<
+ PodBayDoorException>(translator);
+ ...
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Exposing Classes + | +
![]() |
+ ![]() |
+ ![]() |
+
+Now let's expose a C++ class to Python.
++Consider a C++ class/struct that we want to expose to Python:
+
+ struct World
+ {
+ void set(std::string msg) { this->msg = msg; }
+ std::string greet() { return msg; }
+ std::string msg;
+ };
+
++We can expose this to Python by writing a corresponding Boost.Python +C++ Wrapper:
+
+ #include <boost/python.hpp>
+ using namespace boost::python;
+
+ BOOST_PYTHON_MODULE(hello)
+ {
+ class_<World>("World")
+ .def("greet", &World::greet)
+ .def("set", &World::set)
+ ;
+ }
+
++Here, we wrote a C++ class wrapper that exposes the member functions +greet and set. Now, after building our module as a shared library, we +may use our class World in Python. Here's a sample Python session:
+
+ >>> import hello
+ >>> planet = hello.World()
+ >>> planet.set('howdy')
+ >>> planet.greet()
+ 'howdy'
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Extracting C++ objects + | +
![]() |
+ ![]() |
+ ![]() |
+
+At some point, we will need to get C++ values out of object instances. This +can be achieved with the extract<T> function. Consider the following:
+
+ double x = o.attr("length"); // compile error
+
++In the code above, we got a compiler error because Boost.Python +object can't be implicitly converted to doubles. Instead, what +we wanted to do above can be achieved by writing:
+
+ double l = extract<double>(o.attr("length"));
+ Vec2& v = extract<Vec2&>(o);
+ assert(l == v.length());
+
++The first line attempts to extract the "length" attribute of the +Boost.Python object o. The second line attempts to extract the +Vec2 object from held by the Boost.Python object o.
++Take note that we said "attempt to" above. What if the Boost.Python +object o does not really hold a Vec2 type? This is certainly +a possibility considering the dynamic nature of Python objects. To +be on the safe side, if the C++ type can't be extracted, an +appropriate exception is thrown. To avoid an exception, we need to +test for extractibility:
+
+ extract<Vec2&> x(o);
+ if (x.check()) {
+ Vec2& v = x(); ...
+
+
+
The astute reader might have noticed that the extract<T>
+facility in fact solves mutable copying problem:
+ dict d = extract<dict>(x.attr("__dict__"));
+ d['whatever'] = 3; #modifies x.__dict__ !
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Functions + | +
![]() |
+ ![]() |
+ ![]() |
+
+In this chapter, we'll look at Boost.Python powered functions in closer +detail. We shall see some facilities to make exposing C++ functions to +Python safe from potential pifalls such as dangling pointers and +references. We shall also see facilities that will make it even easier for +us to expose C++ functions that take advantage of C++ features such as +overloading and default arguments.
+Read on...
+But before you do, you might want to fire up Python 2.2 or later and type +>>> import this.
+
+ >>> import this
+ The Zen of Python, by Tim Peters
+ Beautiful is better than ugly.
+ Explicit is better than implicit.
+ Simple is better than complex.
+ Complex is better than complicated.
+ Flat is better than nested.
+ Sparse is better than dense.
+ Readability counts.
+ Special cases aren't special enough to break the rules.
+ Although practicality beats purity.
+ Errors should never pass silently.
+ Unless explicitly silenced.
+ In the face of ambiguity, refuse the temptation to guess.
+ There should be one-- and preferably only one --obvious way to do it
+ Although that way may not be obvious at first unless you're Dutch.
+ Now is better than never.
+ Although never is often better than *right* now.
+ If the implementation is hard to explain, it's a bad idea.
+ If the implementation is easy to explain, it may be a good idea.
+ Namespaces are one honking great idea -- let's do more of those!
+
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Inheritance + | +
![]() |
+ ![]() |
+ ![]() |
+
+In the previous examples, we dealt with classes that are not polymorphic. +This is not often the case. Much of the time, we will be wrapping +polymorphic classes and class hierarchies related by inheritance. We will +often have to write Boost.Python wrappers for classes that are derived from +abstract base classes.
++Consider this trivial inheritance structure:
+
+ struct Base { virtual ~Base(); };
+ struct Derived : Base {};
+
++And a set of C++ functions operating on Base and Derived object +instances:
+
+ void b(Base*);
+ void d(Derived*);
+ Base* factory() { return new Derived; }
+
++We've seen how we can wrap the base class Base:
+
+ class_<Base>("Base")
+ /*...*/
+ ;
+
++Now we can inform Boost.Python of the inheritance relationship between +Derived and its base class Base. Thus:
+
+ class_<Derived, bases<Base> >("Derived")
+ /*...*/
+ ;
+
++Doing so, we get some things for free:
++Now, we shall expose the C++ free functions b and d and factory:
+
+ def("b", b);
+ def("d", d);
+ def("factory", factory);
+
++Note that free function factory is being used to generate new +instances of class Derived. In such cases, we use +return_value_policy<manage_new_object> to instruct Python to adopt +the pointer to Base and hold the instance in a new Python Base +object until the the Python object is destroyed. We shall see more of +Boost.Python +call policies later.
+
+ // Tell Python to take ownership of factory's result
+ def("factory", factory,
+ return_value_policy<manage_new_object>());
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Iterators + | +
![]() |
+ ![]() |
+ ![]() |
+
+In C++, and STL in particular, we see iterators everywhere. Python also has +iterators, but these are two very different beasts.
++C++ iterators:
++Python Iterators:
++The typical Python iteration protocol: for y in x... is as follows:
+
+ iter iter = x.__iter__() #get iterator
+ try:
+ while 1:
+ y = iter.next() #get each item
+ ... #process y
+ except StopIteration: pass #iterator exhausted
+
++Boost.Python provides some mechanisms to make C++ iterators play along +nicely as Python iterators. What we need to do is to produce +appropriate __iter__ function from C++ iterators that is compatible +with the Python iteration protocol. For example:
+
+ object get_iterator = iterator<vector<int> >();
+ object iter = get_iterator(v);
+ object first = iter.next();
+
++Or for use in class_<>:
+
+ .def("__iter__", iterator<vector<int> >())
+
++range
++We can create a Python savvy iterator using the range function:
++Here, start/finish may be one of:
++iterator
++Given a container T, iterator is a shortcut that simply calls range +with &T::begin, &T::end.
++Let's put this into action... Here's an example from some hypothetical +bogon Particle accelerator code:
+
+ f = Field()
+ for x in f.pions:
+ smash(x)
+ for y in f.bogons:
+ count(y)
+
++Now, our C++ Wrapper:
+
+ class_<F>("Field")
+ .property("pions", range(&F::p_begin, &F::p_end))
+ .property("bogons", range(&F::b_begin, &F::b_end));
+
+![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Object Interface + | +
![]() |
+ ![]() |
+ ![]() |
+
+Python is dynamically typed, unlike C++ which is statically typed. Python +variables may hold an integers, a float, list, dict, tuple, str, long etc., +among other things. In the viewpoint of Boost.Python and C++, these +Pythonic variables are just instances of class object. We shall see in +this chapter how to deal with Python objects.
++As mentioned, one of the goals of Boost.Python is to provide a +bidirectional mapping between C++ and Python while maintaining the Python +feel. Boost.Python C++ objects are as close as possible to Python. This +should minimize the learning curve significantly.
+
+
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + QuickStart + | +
![]() |
+ ![]() |
+ ![]() |
+
+The Boost Python Library is a framework for interfacing Python and +C++. It allows you to quickly and seamlessly expose C++ classes +functions and objects to Python, and vice-versa, using no special +tools -- just your C++ compiler. It is designed to wrap C++ interfaces +non-intrusively, so that you should not have to change the C++ code at +all in order to wrap it, making Boost.Python ideal for exposing +3rd-party libraries to Python. The library's use of advanced +metaprogramming techniques simplifies its syntax for users, so that +wrapping code takes on the look of a kind of declarative interface +definition language (IDL).
++Following C/C++ tradition, let's start with the "hello, world". A C++ +Function:
+
+ char const* greet()
+ {
+ return "hello, world";
+ }
+
++can be exposed to Python by writing a Boost.Python wrapper:
+
+ #include <boost/python.hpp>
+ using namespace boost::python;
+
+ BOOST_PYTHON_MODULE(hello)
+ {
+ def("greet", greet);
+ }
+
++That's it. We're done. We can now build this as a shared library. The +resulting DLL is now visible to Python. Here's a sample Python session:
+
+ >>> import hello
+ >>> print hello.greet()
+ hello, world
+
+Next stop... Building your Hello World module from start to finish...
![]() |
+ ![]() |
+ ![]() |
+
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
+ |
+ + Boost Python Tutorial + | +
| Table of contents | +
| + QuickStart + | +
| + Building Hello World + | +
| + Exposing Classes + | +
| + Constructors + | +
| + Class Data Members + | +
| + Class Properties + | +
| + Inheritance + | +
| + Class Virtual Functions + | +
| + Class Operators/Special Functions + | +
| + Functions + | +
| + Call Policies + | +
| + Default Arguments + | +
| + Object Interface + | +
| + Basic Interface + | +
| + Derived Object types + | +
| + Extracting C++ objects + | +
| + Iterators + | +
| + Exception Translation + | +
Copyright © 2002 David Abrahams
Copyright © 2002 Joel de Guzman
+Permission to copy, use, modify, sell and distribute this document
+ is granted provided this copyright notice appears in all copies. This document
+ is provided "as is" without express or implied warranty, and with
+ no claim as to its suitability for any purpose.
|
+ |
+
+
+ Boost.Python+ +Header <boost/python.hpp>+ |
+
This is a convenience header which #includes all of the public + interface headers that are part of the Boost.Python library
++# include <args.hpp> +# include <args_fwd.hpp> +# include <back_reference.hpp> +# include <bases.hpp> +# include <borrowed.hpp> +# include <call.hpp> +# include <call_method.hpp> +# include <class.hpp> +# include <copy_const_reference.hpp> +# include <copy_non_const_reference.hpp> +# include <data_members.hpp> +# include <def.hpp> +# include <default_call_policies.hpp> +# include <dict.hpp> +# include <enum.hpp> +# include <errors.hpp> +# include <exception_translator.hpp> +# include <extract.hpp> +# include <handle.hpp> +# include <has_back_reference.hpp> +# include <implicit.hpp> +# include <init.hpp> +# include <instance_holder.hpp> +# include <iterator.hpp> +# include <list.hpp> +# include <long.hpp> +# include <lvalue_from_pytype.hpp> +# include <make_function.hpp> +# include <manage_new_object.hpp> +# include <module.hpp> +# include <numeric.hpp> +# include <object.hpp> +# include <object_protocol.hpp> +# include <object_protocol_core.hpp> +# include <operators.hpp> +# include <other.hpp> +# include <overloads.hpp> +# include <pointee.hpp> +# include <ptr.hpp> +# include <reference_existing_object.hpp> +# include <return_internal_reference.hpp> +# include <return_value_policy.hpp> +# include <scope.hpp> +# include <self.hpp> +# include <slice_nil.hpp> +# include <str.hpp> +# include <to_python_converter.hpp> +# include <to_python_indirect.hpp> +# include <to_python_value.hpp> +# include <tuple.hpp> +# include <type_id.hpp> +# include <with_custodian_and_ward.hpp> ++ +
Revised + + 08 October, 2002 + +
+ +© Copyright Dave Abrahams 2002. All Rights + Reserved.
+ + + diff --git a/example/tutorial/Jamfile b/example/tutorial/Jamfile new file mode 100644 index 00000000..c05463c1 --- /dev/null +++ b/example/tutorial/Jamfile @@ -0,0 +1,15 @@ +# Hello World Example from the tutorial +# [Joel de Guzman 10/9/2002] + +# Specify our location in the boost project hierarchy +subproject libs/python/example/tutorial ; + +# Include definitions needed for Python modules +SEARCH on python.jam = $(BOOST_BUILD_PATH) ; +include python.jam ; + +extension hello # Declare a Python extension called hello +: hello.cpp # source +