diff --git a/doc/tutorial/doc/quickstart.txt b/doc/tutorial/doc/quickstart.txt new file mode 100644 index 00000000..2c1116cb --- /dev/null +++ b/doc/tutorial/doc/quickstart.txt @@ -0,0 +1,1247 @@ +[doc Boost Python Tutorial] + +[def __note__ [$theme/note.gif]] +[def __alert__ [$theme/alert.gif]] +[def __detail__ [$theme/lens.gif]] +[def __tip__ [$theme/bulb.gif]] +[def :-) [$theme/smiley.gif]] + +[page 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). + +[h2 Hello World] + +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 + 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...]]] + +[page Building Hello World] + +[h2 From Start To Finish] + +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]. + +[blurb __detail__ [*Building without bjam][br][br] + +Besides bjam, there are of course other ways to get your module built. +What's written here should not be taken as "the one and only way". +There are of course other build tools apart from [^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 building.html]. +After this brief ['bjam] tutorial, we should have built two DLLs: + +* boost_python.dll +* hello.pyd + +if you are on Windows, and + +* libboost_python.so +* hello.so + +if you are on Unix. + +The tutorial example can be found in the directory: +[^libs/python/example/tutorial]. There, you can find: + +* hello.cpp +* Jamfile + +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 or somewhere in your path such that [^bjam] can be executed in +the command line. Pre-built Boost.Jam executables are available for most +platforms. For example, a pre-built Microsoft Windows bjam executable can +be downloaded [@http://boost.sourceforge.net/jam-executables/bin.ntx86/bjam.zip here]. +The complete list of bjam pre-built +executables can be found [@../../../../../tools/build/index.html#Jam here]. + +[h2 Lets Jam!] +[$theme/jam.png] + +Here is our minimalist Jamfile: + +[pre + 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 + ../../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: + +[pre + subproject libs/python/example/tutorial ; +] + +Then we will include the definitions needed by Python modules: + +[pre + SEARCH on python.jam = $(BOOST_BUILD_PATH) ; + include python.jam ; +] + +Finally we declare our [^hello] extension: + +[pre + extension hello # Declare a Python extension called hello + : hello.cpp # source + ../../build/boost_python # dependencies + ; +] + +[h2 Running bjam] + +['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. __note__ 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 +[@../../../../../tools/build/index.html Building Boost Libraries] for +further details. + +It should be building now: + +[pre + cd C:\dev\boost\libs\python\example\tutorial + bjam -sTOOLS=msvc + ...patience... + ...found 1703 targets... + ...updating 40 targets... +] + +And so on... Finally: + +[pre + 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: + +* boost_python.dll +* hello.pyd + +if you are on Windows, and + +* libboost_python.so +* hello.so + +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!]] + +[page 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 + using namespace boost::python; + + BOOST_PYTHON_MODULE(hello) + { + class_("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' + +[page:1 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_] about the constructor we want to +expose instead. + + #include + using namespace boost::python; + + BOOST_PYTHON_MODULE(hello) + { + class_("World", init()) + .def("greet", &World::greet) + .def("set", &World::set) + ; + } + +[^init()] 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", init()) + .def(init()) + .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", no_init) + +This actually adds an [^__init__] method which always raises a +Python RuntimeError exception. + +[page:1 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", init()) + .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]. + +[pre + >>> x.name = 'e' # can't change name + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: can't set attribute +] + +[page:1 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") + .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) + +[page:1 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") + /*...*/ + ; + +Now we can inform Boost.Python of the inheritance relationship between +[^Derived] and its base class [^Base]. Thus: + + class_ >("Derived") + /*...*/ + ; + +Doing so, we get some things for free: + +# Derived automatically inherits all of Base's Python methods (wrapped C++ member functions) +# [*If] Base is polymorphic, [^Derived] objects which have been passed to Python via a pointer or reference to [^Base] can be passed where a pointer or reference to [^Derived] is expected. + +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] 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.html call policies] later. + + // Tell Python to take ownership of factory's result + def("factory", factory, + return_value_policy()); + +[page:1 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(self, "f"); } + PyObject* self; + }; + +[blurb __detail__ [*member function and methods][br][br] Python, 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. + +[blurb __note__ [*Why do we need BaseWrap?][br][br] + +['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."[br][br] + +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.[br][br] + +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", 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. + +[h2 Deriving a Python class] + +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: + +# [^call_f(derived)] is called in Python +# This corresponds to [^def("call_f", call_f);]. Boost.Python dispatches this call. +# [^int call_f(Base& b) { return b.f(); }] accepts the call. +# The overridden virtual function [^f] of [^BaseWrap] is called. +# [^call_method(self, "f");] dispatches the call back to Python. +# [^def f(self): return 42] is finally called. + +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(self, "f"); } + static int default_f(Base* b) { return b->Base::f(); } // <<=== added + PyObject* self; + }; + +then, our Boost.Python wrapper: + + class_("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_] 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 + +[page:1 Class Operators/Special Functions] + +[h2 Python Operators] + +C is well known for the abundance of operators. 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") + .def(self + int()) // __add__ + .def(int() + self) // __radd__ + .def(self - self) // __sub__ + .def(self - int()) // __rsub__ + .def(self += int()) // __iadd__ + .def(self -= other()) + .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()] in place of an actual +[^T] instance when writing "self expressions". + +[h2 Special Methods] + +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_() + .def(float_(self)) // __float__ + .def(pow(self, other)) // __pow__ + .def(abs(self)) // __abs__ + .def(str(self)) // __str__ + ; + +Need we say more? + +[blurb __detail__ What is the business of [^operator<<] [^.def(str(self))]? +Well, the method [^str] requires the [^operator<<] to do its work (i.e. +[^operator<<] is used by the method defined by def(str(self)).] + +[page 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]. + +[pre + >>> 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! +] + +[page:1 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: + +# [^f] is called passing in a reference to [^y] and a pointer to [^z] +# A reference to [^y.x] is returned +# [^y] is deleted. [^x] is a dangling reference +# [^x.some_method()] is called +# [*BOOM!] + +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: + +# [^f] is called passing in a reference to [^y] and a pointer to [^z] +# A pointer to [^z] is held by [^y] +# A reference to [^y.x] is returned +# [^z] is deleted. [^y.z] is a dangling pointer +# [^y.z_value()] is called +# [^z->value()] is called +# [*BOOM!] + +[h2 Call Policies] + +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: [^Y& y]). + +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 > > + +Here is the list of predefined call policies. A complete reference detailing +these can be found [@../../v2/reference.html#models_of_call_policies here]. + +* [*with_custodian_and_ward][br] Ties lifetimes of the arguments +* [*with_custodian_and_ward_postcall][br] Ties lifetimes of the arguments and results +* [*return_internal_reference][br] Ties lifetime of one argument to that of result +* [*return_value_policy with T one of:][br] +* [*reference_existing_object][br]naďve (dangerous) approach +* [*copy_const_reference][br]Boost.Python v1 approach +* [*copy_non_const_reference][br] +* [*manage_new_object][br] Adopt a pointer and hold the instance + +[blurb :-) [*Remember the Zen, Luke:][br][br] +"Explicit is better than implicit"[br] +"In the face of ambiguity, refuse the temptation to guess"[br]] + +[page:1 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: + +* have default arguments, or +* are overloaded with a common sequence of initial arguments + +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 >()) + +Notice the use of [^init<...>] and [^optional<...>] to signify the default +(optional arguments). + +[page Object Interface] + +Python is dynamically typed, unlike C++ which is statically typed. Python +variables may hold an integer, 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++ [^object]s are as close as possible to Python. This +should minimize the learning curve significantly. + +[$theme/python.png] + +[page:1 Basic Interface] + +Class [^object] wraps [^PyObject*]. All the intricacies of dealing with +[^PyObject]s such as managing reference counting are handled by the +[^object] class. C++ object interoperability is seamless. Boost.Python C++ +[^object]s can in fact be explicitly constructed from any C++ object. + +To illustrate, this Python code snippet: + + def f(x, y): + if (y == 'foo'): + x[3:7] = 'bar' + else: + x.items += y(3, x) + return x + + def getfunc(): + return f; + +Can be rewritten in C++ using Boost.Python facilities this way: + + object f(object x, object y) { + if (y == "foo") + x.slice(3,7) = "bar"; + else + x.attr("items") += y(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. + +[page:1 Derived Object types] + +Boost.Python comes with a set of derived [^object] types corresponding to +that of Python's: + +* list +* dict +* tuple +* str +* long_ +* enum + +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 [^object]s 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++. + +__alert__ [*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 + +[h2 class_ as objects] + +Due to the dynamic nature of Boost.Python objects, any [^class_] 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", init()) + .def_readonly("length", &Point::length) + .def_readonly("angle", &Point::angle) + )(3.0, 4.0); + + assert(vec345.attr("length") == 5.0); + +[page:1 Extracting C++ objects] + +At some point, we will need to get C++ values out of object instances. This +can be achieved with the [^extract] 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 [^double]s. Instead, what +we wanted to do above can be achieved by writing: + + double l = extract(o.attr("length")); + Vec2& v = extract(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 [^object]s. 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 x(o); + if (x.check()) { + Vec2& v = x(); ... + +__tip__ The astute reader might have noticed that the [^extract] +facility in fact solves the mutable copying problem: + + dict d = extract(x.attr("__dict__")); + d['whatever'] = 3; # modifies x.__dict__ ! + + +[page:1 Enums] + +Boost.Python has a nifty facility to capture and wrap C++ enums. While +Python has no [^enum] type, we'll often want to expose our C++ enums to +Python as an [^int]. Boost.Python's enum facility makes this easy while +taking care of the proper conversions from Python's dynamic typing to C++'s +strong static typing (in C++, ints cannot be implicitly converted to +enums). To illustrate, given a C++ enum: + + enum choice { red, blue }; + +the construct: + + enum_("choice") + .value("red", red) + .value("blue", blue) + ; + +can be used to expose to Python. The new enum type is created in the +current [^scope()], which is usually the current module. The snippet above +creates a Python class derived from Python's [^int] type which is +associated with the C++ type passed as its first parameter. + +[blurb __detail__ [*what is a scope?][br][br] The scope is a class that has an +associated global Python object which controls the Python namespace in +which new extension classes and wrapped functions will be defined as +attributes. Details can be found [@../../v2/scope.html here].] + +You can access those values in Python as + + >>> my_module.choice.red + my_module.choice.red + +where my_module is the module where the enum is declared. You can also +create a new scope around a class: + + scope in_X(class_("X") + .def( ... ) + .def( ... ) + ); + + // Expose X::nested as X.nested + enum_("nested") + .value("red", red) + .value("blue", blue) + ; + +[page Iterators] + +In C++, and STL in particular, we see iterators everywhere. Python also has +iterators, but these are two very different beasts. + +[*C++ iterators:] + +* C++ has 5 type categories (random-access, bidirectional, forward, input, output) +* There are 2 Operation categories: reposition, access +* A pair of iterators is needed to represent a (first/last) range. + +[*Python Iterators:] + +* 1 category (forward) +* 1 operation category (next()) +* Raises StopIteration exception at end + +The typical Python iteration protocol: [^[*for y in x...]] is as follows: + + 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 >(); + object iter = get_iterator(v); + object first = iter.next(); + +Or for use in class_<>: + + .def("__iter__", iterator >()) + +[*range] + +We can create a Python savvy iterator using the range function: + +* range(start, finish) +* range(start, finish) + +Here, start/finish may be one of: + +* member data pointers +* member function pointers +* adaptable function object (use Target parameter) + +[*iterator] + +* 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_("Field") + .property("pions", range(&F::p_begin, &F::p_end)) + .property("bogons", range(&F::b_begin, &F::b_end)); + +[page 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); + ... + diff --git a/doc/tutorial/doc/theme/alert.gif b/doc/tutorial/doc/theme/alert.gif new file mode 100644 index 00000000..3c46e712 Binary files /dev/null and b/doc/tutorial/doc/theme/alert.gif differ diff --git a/doc/tutorial/doc/theme/arrow.gif b/doc/tutorial/doc/theme/arrow.gif new file mode 100644 index 00000000..e33db0fb Binary files /dev/null and b/doc/tutorial/doc/theme/arrow.gif differ diff --git a/doc/tutorial/doc/theme/bkd.gif b/doc/tutorial/doc/theme/bkd.gif new file mode 100644 index 00000000..6921a7e4 Binary files /dev/null and b/doc/tutorial/doc/theme/bkd.gif differ diff --git a/doc/tutorial/doc/theme/bkd2.gif b/doc/tutorial/doc/theme/bkd2.gif new file mode 100644 index 00000000..e67a40ea Binary files /dev/null and b/doc/tutorial/doc/theme/bkd2.gif differ diff --git a/doc/tutorial/doc/theme/bulb.gif b/doc/tutorial/doc/theme/bulb.gif new file mode 100644 index 00000000..5b6c354c Binary files /dev/null and b/doc/tutorial/doc/theme/bulb.gif differ diff --git a/doc/tutorial/doc/theme/bullet.gif b/doc/tutorial/doc/theme/bullet.gif new file mode 100644 index 00000000..da787e2e Binary files /dev/null and b/doc/tutorial/doc/theme/bullet.gif differ diff --git a/doc/tutorial/doc/theme/c++boost.gif b/doc/tutorial/doc/theme/c++boost.gif new file mode 100644 index 00000000..8a5a50ca Binary files /dev/null and b/doc/tutorial/doc/theme/c++boost.gif differ diff --git a/doc/tutorial/doc/theme/jam.png b/doc/tutorial/doc/theme/jam.png new file mode 100644 index 00000000..f1ad7558 --- /dev/null +++ b/doc/tutorial/doc/theme/jam.png @@ -0,0 +1 @@ +‰PNG diff --git a/doc/tutorial/doc/theme/l_arr.gif b/doc/tutorial/doc/theme/l_arr.gif new file mode 100644 index 00000000..1d842490 Binary files /dev/null and b/doc/tutorial/doc/theme/l_arr.gif differ diff --git a/doc/tutorial/doc/theme/l_arr_disabled.gif b/doc/tutorial/doc/theme/l_arr_disabled.gif new file mode 100644 index 00000000..e3af82a1 Binary files /dev/null and b/doc/tutorial/doc/theme/l_arr_disabled.gif differ diff --git a/doc/tutorial/doc/theme/lens.gif b/doc/tutorial/doc/theme/lens.gif new file mode 100644 index 00000000..fdf516df Binary files /dev/null and b/doc/tutorial/doc/theme/lens.gif differ diff --git a/doc/tutorial/doc/theme/note.gif b/doc/tutorial/doc/theme/note.gif new file mode 100644 index 00000000..24f9ae75 Binary files /dev/null and b/doc/tutorial/doc/theme/note.gif differ diff --git a/doc/tutorial/doc/theme/python.png b/doc/tutorial/doc/theme/python.png new file mode 100644 index 00000000..f1ad7558 --- /dev/null +++ b/doc/tutorial/doc/theme/python.png @@ -0,0 +1 @@ +‰PNG diff --git a/doc/tutorial/doc/theme/r_arr.gif b/doc/tutorial/doc/theme/r_arr.gif new file mode 100644 index 00000000..2dcdad11 Binary files /dev/null and b/doc/tutorial/doc/theme/r_arr.gif differ diff --git a/doc/tutorial/doc/theme/r_arr_disabled.gif b/doc/tutorial/doc/theme/r_arr_disabled.gif new file mode 100644 index 00000000..2100f78b Binary files /dev/null and b/doc/tutorial/doc/theme/r_arr_disabled.gif differ diff --git a/doc/tutorial/doc/theme/smiley.gif b/doc/tutorial/doc/theme/smiley.gif new file mode 100644 index 00000000..a965770e Binary files /dev/null and b/doc/tutorial/doc/theme/smiley.gif differ diff --git a/doc/tutorial/doc/theme/style.css b/doc/tutorial/doc/theme/style.css new file mode 100644 index 00000000..53a6205e --- /dev/null +++ b/doc/tutorial/doc/theme/style.css @@ -0,0 +1,170 @@ +body +{ + background-image: url(bkd.gif); + background-color: #FFFFFF; + margin: 1em 2em 1em 2em; +} + +h1 { font-family: Verdana, Arial, Helvetica, sans-serif; font-weight: bold; text-align: left; } +h2 { font: 140% sans-serif; font-weight: bold; text-align: left; } +h3 { font: 120% sans-serif; font-weight: bold; text-align: left; } +h4 { font: bold 100% sans-serif; font-weight: bold; text-align: left; } +h5 { font: italic 100% sans-serif; font-weight: bold; text-align: left; } +h6 { font: small-caps 100% sans-serif; font-weight: bold; text-align: left; } + +pre +{ + border-top: gray 1pt solid; + border-right: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + + padding-top: 2pt; + padding-right: 2pt; + padding-left: 2pt; + padding-bottom: 2pt; + + display: block; + font-family: "courier new", courier, mono; + background-color: #eeeeee; font-size: small +} + +code +{ + font-family: "Courier New", Courier, mono; + font-size: small +} + +tt +{ + display: inline; + font-family: "Courier New", Courier, mono; + color: #000099; + font-size: small +} + +p +{ + text-align: justify; + font-family: Georgia, "Times New Roman", Times, serif +} + +ul +{ + list-style-image: url(bullet.gif); + font-family: Georgia, "Times New Roman", Times, serif +} + +ol +{ + font-family: Georgia, "Times New Roman", Times, serif +} + +a +{ + font-weight: bold; + color: #003366; + text-decoration: none; +} + +a:hover { color: #8080FF; } + +.literal { color: #666666; font-style: italic} +.keyword { color: #000099} +.identifier {} +.comment { font-style: italic; color: #990000} +.special { color: #800040} +.preprocessor { color: #FF0000} +.string { font-style: italic; color: #666666} +.copyright { color: #666666; font-size: small} +.white_bkd { background-color: #FFFFFF} +.dk_grey_bkd { background-color: #999999} +.quotes { color: #666666; font-style: italic; font-weight: bold} + +.note_box +{ + display: block; + + border-top: gray 1pt solid; + border-right: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + + padding-right: 12pt; + padding-left: 12pt; + padding-bottom: 12pt; + padding-top: 12pt; + + font-family: Arial, Helvetica, sans-serif; + background-color: #E2E9EF; + font-size: small; text-align: justify +} + +.table_title +{ + background-color: #648CCA; + + font-family: Verdana, Arial, Helvetica, sans-serif; color: #FFFFFF; + font-weight: bold +; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px +} + +.table_cells +{ + background-color: #E2E9EF; + + font-family: Geneva, Arial, Helvetica, san-serif; + font-size: small +; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px +} + +.toc +{ + DISPLAY: block; + background-color: #E2E9EF + font-family: Arial, Helvetica, sans-serif; + + border-top: gray 1pt solid; + border-left: gray 1pt solid; + border-bottom: gray 1pt solid; + border-right: gray 1pt solid; + + padding-top: 24pt; + padding-right: 24pt; + padding-left: 24pt; + padding-bottom: 24pt; +} + +.toc_title +{ + background-color: #648CCA; + padding-top: 4px; + padding-right: 4px; + padding-bottom: 4px; + padding-left: 4px; + font-family: Geneva, Arial, Helvetica, san-serif; + color: #FFFFFF; + font-weight: bold +} + +.toc_cells +{ + background-color: #E2E9EF; + padding-top: 4px; + padding-right: 4px; + padding-bottom: 4px; + padding-left: 4px; + font-family: Geneva, Arial, Helvetica, san-serif; + font-size: small +} + +div.logo +{ + float: right; +} + +.toc_cells_L0 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 4px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L1 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 44px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L2 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 88px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L3 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 122px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } +.toc_cells_L4 { background-color: #E2E9EF; padding-top: 4px; padding-right: 4px; padding-bottom: 4px; padding-left: 166px; font-family: Geneva, Arial, Helvetica, san-serif; font-size: small } diff --git a/doc/tutorial/doc/theme/u_arr.gif b/doc/tutorial/doc/theme/u_arr.gif new file mode 100644 index 00000000..44bf99eb Binary files /dev/null and b/doc/tutorial/doc/theme/u_arr.gif differ diff --git a/doc/tutorial/index.html b/doc/tutorial/index.html new file mode 100644 index 00000000..4e1333da --- /dev/null +++ b/doc/tutorial/index.html @@ -0,0 +1,126 @@ + + + +Boost Python Tutorial + + + + + + + + + +
+ + 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 +
+ Enums +
+ Iterators +
+ Exception Translation +
+
+
+ +