diff --git a/doc/PyConDC_2003/bpl.html b/doc/PyConDC_2003/bpl.html index a9ecebd5..4ee77260 100755 --- a/doc/PyConDC_2003/bpl.html +++ b/doc/PyConDC_2003/bpl.html @@ -1,1127 +1,16 @@ - - - - - - -Building Hybrid Systems with Boost.Python - - - - - - - - -
-

Building Hybrid Systems with Boost.Python

- --- - - - - - - - - - - - - - -
Author:David Abrahams
Contact:dave@boost-consulting.com
Organization:Boost Consulting
Date:2003-03-19
Author:Ralf W. Grosse-Kunstleve
Copyright:Copyright David Abrahams and Ralf W. Grosse-Kunstleve 2003. All rights reserved
-
-

Table of Contents

- -
-
-

Abstract

-

Boost.Python is an open source C++ library which provides a concise -IDL-like interface for binding C++ classes and functions to -Python. Leveraging the full power of C++ compile-time introspection -and of recently developed metaprogramming techniques, this is achieved -entirely in pure C++, without introducing a new syntax. -Boost.Python's rich set of features and high-level interface make it -possible to engineer packages from the ground up as hybrid systems, -giving programmers easy and coherent access to both the efficient -compile-time polymorphism of C++ and the extremely convenient run-time -polymorphism of Python.

-
-
-

Introduction

-

Python and C++ are in many ways as different as two languages could -be: while C++ is usually compiled to machine-code, Python is -interpreted. Python's dynamic type system is often cited as the -foundation of its flexibility, while in C++ static typing is the -cornerstone of its efficiency. C++ has an intricate and difficult -compile-time meta-language, while in Python, practically everything -happens at runtime.

-

Yet for many programmers, these very differences mean that Python and -C++ complement one another perfectly. Performance bottlenecks in -Python programs can be rewritten in C++ for maximal speed, and -authors of powerful C++ libraries choose Python as a middleware -language for its flexible system integration capabilities. -Furthermore, the surface differences mask some strong similarities:

- -

Given Python's rich 'C' interoperability API, it should in principle -be possible to expose C++ type and function interfaces to Python with -an analogous interface to their C++ counterparts. However, the -facilities provided by Python alone for integration with C++ are -relatively meager. Compared to C++ and Python, 'C' has only very -rudimentary abstraction facilities, and support for exception-handling -is completely missing. 'C' extension module writers are required to -manually manage Python reference counts, which is both annoyingly -tedious and extremely error-prone. Traditional extension modules also -tend to contain a great deal of boilerplate code repetition which -makes them difficult to maintain, especially when wrapping an evolving -API.

-

These limitations have lead to the development of a variety of wrapping -systems. SWIG is probably the most popular package for the -integration of C/C++ and Python. A more recent development is SIP, -which was specifically designed for interfacing Python with the Qt -graphical user interface library. Both SWIG and SIP introduce their -own specialized languages for customizing inter-language bindings. -This has certain advantages, but having to deal with three different -languages (Python, C/C++ and the interface language) also introduces -practical and mental difficulties. The CXX package demonstrates an -interesting alternative. It shows that at least some parts of -Python's 'C' API can be wrapped and presented through a much more -user-friendly C++ interface. However, unlike SWIG and SIP, CXX does -not include support for wrapping C++ classes as new Python types.

-

The features and goals of Boost.Python overlap significantly with -many of these other systems. That said, Boost.Python attempts to -maximize convenience and flexibility without introducing a separate -wrapping language. Instead, it presents the user with a high-level -C++ interface for wrapping C++ classes and functions, managing much of -the complexity behind-the-scenes with static metaprogramming. -Boost.Python also goes beyond the scope of earlier systems by -providing:

- -

The key insight that sparked the development of Boost.Python is that -much of the boilerplate code in traditional extension modules could be -eliminated using C++ compile-time introspection. Each argument of a -wrapped C++ function must be extracted from a Python object using a -procedure that depends on the argument type. Similarly the function's -return type determines how the return value will be converted from C++ -to Python. Of course argument and return types are part of each -function's type, and this is exactly the source from which -Boost.Python deduces most of the information required.

-

This approach leads to user guided wrapping: as much information is -extracted directly from the source code to be wrapped as is possible -within the framework of pure C++, and some additional information is -supplied explicitly by the user. Mostly the guidance is mechanical -and little real intervention is required. Because the interface -specification is written in the same full-featured language as the -code being exposed, the user has unprecedented power available when -she does need to take control.

-
-
-

Boost.Python Design Goals

-

The primary goal of Boost.Python is to allow users to expose C++ -classes and functions to Python using nothing more than a C++ -compiler. In broad strokes, the user experience should be one of -directly manipulating C++ objects from Python.

-

However, it's also important not to translate all interfaces too -literally: the idioms of each language must be respected. For -example, though C++ and Python both have an iterator concept, they are -expressed very differently. Boost.Python has to be able to bridge the -interface gap.

-

It must be possible to insulate Python users from crashes resulting -from trivial misuses of C++ interfaces, such as accessing -already-deleted objects. By the same token the library should -insulate C++ users from low-level Python 'C' API, replacing -error-prone 'C' interfaces like manual reference-count management and -raw PyObject pointers with more-robust alternatives.

-

Support for component-based development is crucial, so that C++ types -exposed in one extension module can be passed to functions exposed in -another without loss of crucial information like C++ inheritance -relationships.

-

Finally, all wrapping must be non-intrusive, without modifying or -even seeing the original C++ source code. Existing C++ libraries have -to be wrappable by third parties who only have access to header files -and binaries.

-
-
-

Hello Boost.Python World

-

And now for a preview of Boost.Python, and how it improves on the raw -facilities offered by Python. Here's a function we might want to -expose:

-
-char const* greet(unsigned x)
-{
-   static char const* const msgs[] = { "hello", "Boost.Python", "world!" };
-
-   if (x > 2) 
-       throw std::range_error("greet: index out of range");
-
-   return msgs[x];
-}
-
-

To wrap this function in standard C++ using the Python 'C' API, we'd -need something like this:

-
-extern "C" // all Python interactions use 'C' linkage and calling convention
-{
-    // Wrapper to handle argument/result conversion and checking
-    PyObject* greet_wrap(PyObject* args, PyObject * keywords)
-    {
-         int x;
-         if (PyArg_ParseTuple(args, "i", &x))    // extract/check arguments
-         {
-             char const* result = greet(x);      // invoke wrapped function
-             return PyString_FromString(result); // convert result to Python
-         }
-         return 0;                               // error occurred
-    }
-
-    // Table of wrapped functions to be exposed by the module
-    static PyMethodDef methods[] = {
-        { "greet", greet_wrap, METH_VARARGS, "return one of 3 parts of a greeting" }
-        , { NULL, NULL, 0, NULL } // sentinel
-    };
-
-    // module initialization function
-    DL_EXPORT init_hello()
-    {
-        (void) Py_InitModule("hello", methods); // add the methods to the module
-    }
-}
-
-

Now here's the wrapping code we'd use to expose it with Boost.Python:

-
-#include <boost/python.hpp>
-using namespace boost::python;
-BOOST_PYTHON_MODULE(hello)
-{
-    def("greet", greet, "return one of 3 parts of a greeting");
-}
-
-

and here it is in action:

-
->>> import hello
->>> for x in range(3):
-...     print hello.greet(x)
-...
-hello
-Boost.Python
-world!
-
-

Aside from the fact that the 'C' API version is much more verbose, -it's worth noting a few things that it doesn't handle correctly:

- -
-
-

Library Overview

-

This section outlines some of the library's major features. Except as -neccessary to avoid confusion, details of library implementation are -omitted.

-
-

Exposing Classes

-

C++ classes and structs are exposed with a similarly-terse interface. -Given:

-
-struct World
-{
-    void set(std::string msg) { this->msg = msg; }
-    std::string greet() { return msg; }
-    std::string msg;
-};
-
-

The following code will expose it in our extension module:

-
-#include <boost/python.hpp>
-BOOST_PYTHON_MODULE(hello)
-{
-    class_<World>("World")
-        .def("greet", &World::greet)
-        .def("set", &World::set)
-    ;
-}
-
-

Although this code has a certain pythonic familiarity, people -sometimes find the syntax bit confusing because it doesn't look like -most of the C++ code they're used to. All the same, this is just -standard C++. Because of their flexible syntax and operator -overloading, C++ and Python are great for defining domain-specific -(sub)languages -(DSLs), and that's what we've done in Boost.Python. To break it down:

-
-class_<World>("World")
-
-

constructs an unnamed object of type class_<World> and passes -"World" to its constructor. This creates a new-style Python class -called World in the extension module, and associates it with the -C++ type World in the Boost.Python type conversion registry. We -might have also written:

-
-class_<World> w("World");
-
-

but that would've been more verbose, since we'd have to name w -again to invoke its def() member function:

-
-w.def("greet", &World::greet)
-
-

There's nothing special about the location of the dot for member -access in the original example: C++ allows any amount of whitespace on -either side of a token, and placing the dot at the beginning of each -line allows us to chain as many successive calls to member functions -as we like with a uniform syntax. The other key fact that allows -chaining is that class_<> member functions all return a reference -to *this.

-

So the example is equivalent to:

-
-class_<World> w("World");
-w.def("greet", &World::greet);
-w.def("set", &World::set);
-
-

It's occasionally useful to be able to break down the components of a -Boost.Python class wrapper in this way, but the rest of this article -will stick to the terse syntax.

-

For completeness, here's the wrapped class in use:

-
->>> import hello
->>> planet = hello.World()
->>> planet.set('howdy')
->>> planet.greet()
-'howdy'
-
-
-

Constructors

-

Since our World class is just a plain struct, it has an -implicit no-argument (nullary) constructor. Boost.Python exposes the -nullary constructor by default, which is why we were able to write:

-
->>> planet = hello.World()
-
-

However, well-designed classes in any language may require constructor -arguments in order to establish their invariants. Unlike Python, -where __init__ is just a specially-named method, In C++ -constructors cannot be handled like ordinary member functions. In -particular, we can't take their address: &World::World is an -error. The library provides a different interface for specifying -constructors. Given:

-
-struct World
-{
-    World(std::string msg); // added constructor
-    ...
-
-

we can modify our wrapping code as follows:

-
-class_<World>("World", init<std::string>())
-    ...
-
-

of course, a C++ class may have additional constructors, and we can -expose those as well by passing more instances of init<...> to -def():

-
-class_<World>("World", init<std::string>())
-    .def(init<double, double>())
-    ...
-
-

Boost.Python allows wrapped functions, member functions, and -constructors to be overloaded to mirror C++ overloading.

-
-
-

Data Members and Properties

-

Any publicly-accessible data members in a C++ class can be easily -exposed as either readonly or readwrite attributes:

-
-class_<World>("World", init<std::string>())
-    .def_readonly("msg", &World::msg)
-    ...
-
-

and can be used directly in Python:

-
->>> planet = hello.World('howdy')
->>> planet.msg
-'howdy'
-
-

This does not result in adding attributes to the World instance -__dict__, which can result in substantial memory savings when -wrapping large data structures. In fact, no instance __dict__ -will be created at all unless attributes are explicitly added from -Python. Boost.Python owes this capability to the new Python 2.2 type -system, in particular the descriptor interface and property type.

-

In C++, publicly-accessible data members are considered a sign of poor -design because they break encapsulation, and style guides usually -dictate the use of "getter" and "setter" functions instead. In -Python, however, __getattr__, __setattr__, and since 2.2, -property mean that attribute access is just one more -well-encapsulated syntactic tool at the programmer's disposal. -Boost.Python bridges this idiomatic gap by making Python property -creation directly available to users. If msg were private, we -could still expose it as attribute in Python as follows:

-
-class_<World>("World", init<std::string>())
-    .add_property("msg", &World::greet, &World::set)
-    ...
-
-

The example above mirrors the familiar usage of properties in Python -2.2+:

-
->>> class World(object):
-...     __init__(self, msg):
-...         self.__msg = msg
-...     def greet(self):
-...         return self.__msg
-...     def set(self, msg):
-...         self.__msg = msg
-...     msg = property(greet, set)
-
-
-
-

Operator Overloading

-

The ability to write arithmetic operators for user-defined types has -been a major factor in the success of both languages for numerical -computation, and the success of packages like NumPy attests to the -power of exposing operators in extension modules. Boost.Python -provides a concise mechanism for wrapping operator overloads. The -example below shows a fragment from a wrapper for the Boost rational -number library:

-
-class_<rational<int> >("rational_int")
-  .def(init<int, int>()) // constructor, e.g. rational_int(3,4)
-  .def("numerator", &rational<int>::numerator)
-  .def("denominator", &rational<int>::denominator)
-  .def(-self)        // __neg__ (unary minus)
-  .def(self + self)  // __add__ (homogeneous)
-  .def(self * self)  // __mul__
-  .def(self + int()) // __add__ (heterogenous)
-  .def(int() + self) // __radd__
-  ...
-
-

The magic is performed using a simplified application of "expression -templates" [VELD1995], a technique originally developed for -optimization of high-performance matrix algebra expressions. The -essence is that instead of performing the computation immediately, -operators are overloaded to construct a type representing the -computation. In matrix algebra, dramatic optimizations are often -available when the structure of an entire expression can be taken into -account, rather than evaluating each operation "greedily". -Boost.Python uses the same technique to build an appropriate Python -method object based on expressions involving self.

-
-
-

Inheritance

-

C++ inheritance relationships can be represented to Boost.Python by adding -an optional bases<...> argument to the class_<...> template -parameter list as follows:

-
-class_<Derived, bases<Base1,Base2> >("Derived")
-     ...
-
-

This has two effects:

-
    -
  1. When the class_<...> is created, Python type objects -corresponding to Base1 and Base2 are looked up in -Boost.Python's registry, and are used as bases for the new Python -Derived type object, so methods exposed for the Python Base1 -and Base2 types are automatically members of the Derived -type. Because the registry is global, this works correctly even if -Derived is exposed in a different module from either of its -bases.
  2. -
  3. C++ conversions from Derived to its bases are added to the -Boost.Python registry. Thus wrapped C++ methods expecting (a -pointer or reference to) an object of either base type can be -called with an object wrapping a Derived instance. Wrapped -member functions of class T are treated as though they have an -implicit first argument of T&, so these conversions are -neccessary to allow the base class methods to be called for derived -objects.
  4. -
-

Of course it's possible to derive new Python classes from wrapped C++ -class instances. Because Boost.Python uses the new-style class -system, that works very much as for the Python built-in types. There -is one significant detail in which it differs: the built-in types -generally establish their invariants in their __new__ function, so -that derived classes do not need to call __init__ on the base -class before invoking its methods :

-
->>> class L(list):
-...      def __init__(self):
-...          pass
-...
->>> L().reverse()
->>> 
-
-

Because C++ object construction is a one-step operation, C++ instance -data cannot be constructed until the arguments are available, in the -__init__ function:

-
->>> class D(SomeBoostPythonClass):
-...      def __init__(self):
-...          pass
-...
->>> D().some_boost_python_method()
-Traceback (most recent call last):
-  File "<stdin>", line 1, in ?
-TypeError: bad argument type for built-in operation
-
-

This happened because Boost.Python couldn't find instance data of type -SomeBoostPythonClass within the D instance; D's __init__ -function masked construction of the base class. It could be corrected -by either removing D's __init__ function or having it call -SomeBoostPythonClass.__init__(...) explicitly.

-
-
-

Virtual Functions

-

Deriving new types in Python from extension classes is not very -interesting unless they can be used polymorphically from C++. In -other words, Python method implementations should appear to override -the implementation of C++ virtual functions when called through base -class pointers/references from C++. Since the only way to alter the -behavior of a virtual function is to override it in a derived class, -the user must build a special derived class to dispatch a polymorphic -class' virtual functions:

-
-//
-// interface to wrap:
-//
-class Base
-{
- public:
-    virtual int f(std::string x) { return 42; }
-    virtual ~Base();
-};
-
-int calls_f(Base const& b, std::string x) { return b.f(x); }
-
-//
-// Wrapping Code
-//
-
-// Dispatcher class
-struct BaseWrap : Base
-{
-    // Store a pointer to the Python object
-    BaseWrap(PyObject* self_) : self(self_) {}
-    PyObject* self;
-
-    // Default implementation, for when f is not overridden
-    int f_default(std::string x) { return this->Base::f(x); }
-    // Dispatch implementation
-    int f(std::string x) { return call_method<int>(self, "f", x); }
-};
-
-...
-    def("calls_f", calls_f);
-    class_<Base, BaseWrap>("Base")
-        .def("f", &Base::f, &BaseWrap::f_default)
-        ;
-
-

Now here's some Python code which demonstrates:

-
->>> class Derived(Base):
-...     def f(self, s):
-...          return len(s)
-...
->>> calls_f(Base(), 'foo')
-42
->>> calls_f(Derived(), 'forty-two')
-9
-
-

Things to notice about the dispatcher class:

-
    -
  • The key element which allows overriding in Python is the -call_method invocation, which uses the same global type -conversion registry as the C++ function wrapping does to convert its -arguments from C++ to Python and its return type from Python to C++.
  • -
  • Any constructor signatures you wish to wrap must be replicated with -an initial PyObject* argument
  • -
  • The dispatcher must store this argument so that it can be used to -invoke call_method
  • -
  • The f_default member function is needed when the function being -exposed is not pure virtual; there's no other way Base::f can be -called on an object of type BaseWrap, since it overrides f.
  • -
-
-
-

Deeper Reflection on the Horizon?

-

Admittedly, this formula is tedious to repeat, especially on a project -with many polymorphic classes. That it is neccessary reflects some -limitations in C++'s compile-time introspection capabilities: there's -no way to enumerate the members of a class and find out which are -virtual functions. At least one very promising project has been -started to write a front-end which can generate these dispatchers (and -other wrapping code) automatically from C++ headers.

-

Pyste is being developed by Bruno da Silva de Oliveira. It builds on -GCC_XML, which generates an XML version of GCC's internal program -representation. Since GCC is a highly-conformant C++ compiler, this -ensures correct handling of the most-sophisticated template code and -full access to the underlying type system. In keeping with the -Boost.Python philosophy, a Pyste interface description is neither -intrusive on the code being wrapped, nor expressed in some unfamiliar -language: instead it is a 100% pure Python script. If Pyste is -successful it will mark a move away from wrapping everything directly -in C++ for many of our users. It will also allow us the choice to -shift some of the metaprogram code from C++ to Python. We expect that -soon, not only our users but the Boost.Python developers themselves -will be "thinking hybrid" about their own code.

-
-
-
-

Serialization

-

Serialization is the process of converting objects in memory to a -form that can be stored on disk or sent over a network connection. The -serialized object (most often a plain string) can be retrieved and -converted back to the original object. A good serialization system will -automatically convert entire object hierarchies. Python's standard -pickle module is just such a system. It leverages the language's strong -runtime introspection facilities for serializing practically arbitrary -user-defined objects. With a few simple and unintrusive provisions this -powerful machinery can be extended to also work for wrapped C++ objects. -Here is an example:

-
-#include <string>
-
-struct World
-{
-    World(std::string a_msg) : msg(a_msg) {}
-    std::string greet() const { return msg; }
-    std::string msg;
-};
-
-#include <boost/python.hpp>
-using namespace boost::python;
-
-struct World_picklers : pickle_suite
-{
-  static tuple
-  getinitargs(World const& w) { return make_tuple(w.greet()); }
-};
-
-BOOST_PYTHON_MODULE(hello)
-{
-    class_<World>("World", init<std::string>())
-        .def("greet", &World::greet)
-        .def_pickle(World_picklers())
-    ;
-}
-
-

Now let's create a World object and put it to rest on disk:

-
->>> import hello
->>> import pickle
->>> a_world = hello.World("howdy")
->>> pickle.dump(a_world, open("my_world", "w"))
-
-

In a potentially different script on a potentially different -computer with a potentially different operating system:

-
->>> import pickle
->>> resurrected_world = pickle.load(open("my_world", "r"))
->>> resurrected_world.greet()
-'howdy'
-
-

Of course the cPickle module can also be used for faster -processing.

-

Boost.Python's pickle_suite fully supports the pickle protocol -defined in the standard Python documentation. Like a __getinitargs__ -function in Python, the pickle_suite's getinitargs() is responsible for -creating the argument tuple that will be use to reconstruct the pickled -object. The other elements of the Python pickling protocol, -__getstate__ and __setstate__ can be optionally provided via C++ -getstate and setstate functions. C++'s static type system allows the -library to ensure at compile-time that nonsensical combinations of -functions (e.g. getstate without setstate) are not used.

-

Enabling serialization of more complex C++ objects requires a little -more work than is shown in the example above. Fortunately the -object interface (see next section) greatly helps in keeping the -code manageable.

-
-
-

Object interface

-

Experienced 'C' language extension module authors will be familiar -with the ubiquitous PyObject*, manual reference-counting, and the -need to remember which API calls return "new" (owned) references or -"borrowed" (raw) references. These constraints are not just -cumbersome but also a major source of errors, especially in the -presence of exceptions.

-

Boost.Python provides a class object which automates reference -counting and provides conversion to Python from C++ objects of -arbitrary type. This significantly reduces the learning effort for -prospective extension module writers.

-

Creating an object from any other type is extremely simple:

-
-object s("hello, world");  // s manages a Python string
-
-

object has templated interactions with all other types, with -automatic to-python conversions. It happens so naturally that it's -easily overlooked:

-
-object ten_Os = 10 * s[4]; // -> "oooooooooo"
-
-

In the example above, 4 and 10 are converted to Python objects -before the indexing and multiplication operations are invoked.

-

The extract<T> class template can be used to convert Python objects -to C++ types:

-
-double x = extract<double>(o);
-
-

If a conversion in either direction cannot be performed, an -appropriate exception is thrown at runtime.

-

The object type is accompanied by a set of derived types -that mirror the Python built-in types such as list, dict, -tuple, etc. as much as possible. This enables convenient -manipulation of these high-level types from C++:

-
-dict d;
-d["some"] = "thing";
-d["lucky_number"] = 13;
-list l = d.keys();
-
-

This almost looks and works like regular Python code, but it is pure -C++. Of course we can wrap C++ functions which accept or return -object instances.

-
-
-
-

Thinking hybrid

-

Because of the practical and mental difficulties of combining -programming languages, it is common to settle a single language at the -outset of any development effort. For many applications, performance -considerations dictate the use of a compiled language for the core -algorithms. Unfortunately, due to the complexity of the static type -system, the price we pay for runtime performance is often a -significant increase in development time. Experience shows that -writing maintainable C++ code usually takes longer and requires far -more hard-earned working experience than developing comparable Python -code. Even when developers are comfortable working exclusively in -compiled languages, they often augment their systems by some type of -ad hoc scripting layer for the benefit of their users without ever -availing themselves of the same advantages.

-

Boost.Python enables us to think hybrid. Python can be used for -rapidly prototyping a new application; its ease of use and the large -pool of standard libraries give us a head start on the way to a -working system. If necessary, the working code can be used to -discover rate-limiting hotspots. To maximize performance these can -be reimplemented in C++, together with the Boost.Python bindings -needed to tie them back into the existing higher-level procedure.

-

Of course, this top-down approach is less attractive if it is clear -from the start that many algorithms will eventually have to be -implemented in C++. Fortunately Boost.Python also enables us to -pursue a bottom-up approach. We have used this approach very -successfully in the development of a toolbox for scientific -applications. The toolbox started out mainly as a library of C++ -classes with Boost.Python bindings, and for a while the growth was -mainly concentrated on the C++ parts. However, as the toolbox is -becoming more complete, more and more newly added functionality can be -implemented in Python.

-

python_cpp_mix.jpg

-

This figure shows the estimated ratio of newly added C++ and Python -code over time as new algorithms are implemented. We expect this -ratio to level out near 70% Python. Being able to solve new problems -mostly in Python rather than a more difficult statically typed -language is the return on our investment in Boost.Python. The ability -to access all of our code from Python allows a broader group of -developers to use it in the rapid development of new applications.

-
-
-

Development history

-

The first version of Boost.Python was developed in 2000 by Dave -Abrahams at Dragon Systems, where he was privileged to have Tim Peters -as a guide to "The Zen of Python". One of Dave's jobs was to develop -a Python-based natural language processing system. Since it was -eventually going to be targeting embedded hardware, it was always -assumed that the compute-intensive core would be rewritten in C++ to -optimize speed and memory footprint 1. The project also wanted to -test all of its C++ code using Python test scripts 2. The only -tool we knew of for binding C++ and Python was SWIG, and at the time -its handling of C++ was weak. It would be false to claim any deep -insight into the possible advantages of Boost.Python's approach at -this point. Dave's interest and expertise in fancy C++ template -tricks had just reached the point where he could do some real damage, -and Boost.Python emerged as it did because it filled a need and -because it seemed like a cool thing to try.

-

This early version was aimed at many of the same basic goals we've -described in this paper, differing most-noticeably by having a -slightly more cumbersome syntax and by lack of special support for -operator overloading, pickling, and component-based development. -These last three features were quickly added by Ullrich Koethe and -Ralf Grosse-Kunstleve 3, and other enthusiastic contributors arrived -on the scene to contribute enhancements like support for nested -modules and static member functions.

-

By early 2001 development had stabilized and few new features were -being added, however a disturbing new fact came to light: Ralf had -begun testing Boost.Python on pre-release versions of a compiler using -the EDG front-end, and the mechanism at the core of Boost.Python -responsible for handling conversions between Python and C++ types was -failing to compile. As it turned out, we had been exploiting a very -common bug in the implementation of all the C++ compilers we had -tested. We knew that as C++ compilers rapidly became more -standards-compliant, the library would begin failing on more -platforms. Unfortunately, because the mechanism was so central to the -functioning of the library, fixing the problem looked very difficult.

-

Fortunately, later that year Lawrence Berkeley and later Lawrence -Livermore National labs contracted with Boost Consulting for support -and development of Boost.Python, and there was a new opportunity to -address fundamental issues and ensure a future for the library. A -redesign effort began with the low level type conversion architecture, -building in standards-compliance and support for component-based -development (in contrast to version 1 where conversions had to be -explicitly imported and exported across module boundaries). A new -analysis of the relationship between the Python and C++ objects was -done, resulting in more intuitive handling for C++ lvalues and -rvalues.

-

The emergence of a powerful new type system in Python 2.2 made the -choice of whether to maintain compatibility with Python 1.5.2 easy: -the opportunity to throw away a great deal of elaborate code for -emulating classic Python classes alone was too good to pass up. In -addition, Python iterators and descriptors provided crucial and -elegant tools for representing similar C++ constructs. The -development of the generalized object interface allowed us to -further shield C++ programmers from the dangers and syntactic burdens -of the Python 'C' API. A great number of other features including C++ -exception translation, improved support for overloaded functions, and -most significantly, CallPolicies for handling pointers and -references, were added during this period.

-

In October 2002, version 2 of Boost.Python was released. Development -since then has concentrated on improved support for C++ runtime -polymorphism and smart pointers. Peter Dimov's ingenious -boost::shared_ptr design in particular has allowed us to give the -hybrid developer a consistent interface for moving objects back and -forth across the language barrier without loss of information. At -first, we were concerned that the sophistication and complexity of the -Boost.Python v2 implementation might discourage contributors, but the -emergence of Pyste and several other significant feature -contributions have laid those fears to rest. Daily questions on the -Python C++-sig and a backlog of desired improvements show that the -library is getting used. To us, the future looks bright.

-
-
-

Conclusions

-

Boost.Python achieves seamless interoperability between two rich and -complimentary language environments. Because it leverages template -metaprogramming to introspect about types and functions, the user -never has to learn a third syntax: the interface definitions are -written in concise and maintainable C++. Also, the wrapping system -doesn't have to parse C++ headers or represent the type system: the -compiler does that work for us.

-

Computationally intensive tasks play to the strengths of C++ and are -often impossible to implement efficiently in pure Python, while jobs -like serialization that are trivial in Python can be very difficult in -pure C++. Given the luxury of building a hybrid software system from -the ground up, we can approach design with new confidence and power.

-
-
-

Citations

- - -- - - -
[VELD1995]T. Veldhuizen, "Expression Templates," C++ Report, -Vol. 7 No. 5 June 1995, pp. 26-31. -http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
-
-
-

Footnotes

- - - - - -
[1]In retrospect, it seems that "thinking hybrid" from the -ground up might have been better for the NLP system: the -natural component boundaries defined by the pure python -prototype turned out to be inappropriate for getting the -desired performance and memory footprint out of the C++ core, -which eventually caused some redesign overhead on the Python -side when the core was moved to C++.
- - - - - -
[2]We also have some reservations about driving all C++ -testing through a Python interface, unless that's the only way -it will be ultimately used. Any transition across language -boundaries with such different object models can inevitably -mask bugs.
- - - - - -
[3]These features were expressed very differently in v1 of -Boost.Python
-
-
- - - + + Loading...; if nothing happens, please go to http://www.boost-consulting.com/writing/bpl.html. + + diff --git a/doc/PyConDC_2003/bpl.pdf b/doc/PyConDC_2003/bpl.pdf index 486c1d8b..09827aff 100755 Binary files a/doc/PyConDC_2003/bpl.pdf and b/doc/PyConDC_2003/bpl.pdf differ diff --git a/doc/PyConDC_2003/bpl.txt b/doc/PyConDC_2003/bpl.txt index c2ee19aa..e2a77977 100644 --- a/doc/PyConDC_2003/bpl.txt +++ b/doc/PyConDC_2003/bpl.txt @@ -1,947 +1 @@ -+++++++++++++++++++++++++++++++++++++++++++ - Building Hybrid Systems with Boost.Python -+++++++++++++++++++++++++++++++++++++++++++ - -:Author: David Abrahams -:Contact: dave@boost-consulting.com -:organization: `Boost Consulting`_ -:date: $Date$ - -:Author: Ralf W. Grosse-Kunstleve - -:copyright: Copyright David Abrahams and Ralf W. Grosse-Kunstleve 2003. All rights reserved - -.. contents:: Table of Contents - -.. _`Boost Consulting`: http://www.boost-consulting.com - -========== - Abstract -========== - -Boost.Python is an open source C++ library which provides a concise -IDL-like interface for binding C++ classes and functions to -Python. Leveraging the full power of C++ compile-time introspection -and of recently developed metaprogramming techniques, this is achieved -entirely in pure C++, without introducing a new syntax. -Boost.Python's rich set of features and high-level interface make it -possible to engineer packages from the ground up as hybrid systems, -giving programmers easy and coherent access to both the efficient -compile-time polymorphism of C++ and the extremely convenient run-time -polymorphism of Python. - -============== - Introduction -============== - -Python and C++ are in many ways as different as two languages could -be: while C++ is usually compiled to machine-code, Python is -interpreted. Python's dynamic type system is often cited as the -foundation of its flexibility, while in C++ static typing is the -cornerstone of its efficiency. C++ has an intricate and difficult -compile-time meta-language, while in Python, practically everything -happens at runtime. - -Yet for many programmers, these very differences mean that Python and -C++ complement one another perfectly. Performance bottlenecks in -Python programs can be rewritten in C++ for maximal speed, and -authors of powerful C++ libraries choose Python as a middleware -language for its flexible system integration capabilities. -Furthermore, the surface differences mask some strong similarities: - -* 'C'-family control structures (if, while, for...) - -* Support for object-orientation, functional programming, and generic - programming (these are both *multi-paradigm* programming languages.) - -* Comprehensive operator overloading facilities, recognizing the - importance of syntactic variability for readability and - expressivity. - -* High-level concepts such as collections and iterators. - -* High-level encapsulation facilities (C++: namespaces, Python: modules) - to support the design of re-usable libraries. - -* Exception-handling for effective management of error conditions. - -* C++ idioms in common use, such as handle/body classes and - reference-counted smart pointers mirror Python reference semantics. - -Given Python's rich 'C' interoperability API, it should in principle -be possible to expose C++ type and function interfaces to Python with -an analogous interface to their C++ counterparts. However, the -facilities provided by Python alone for integration with C++ are -relatively meager. Compared to C++ and Python, 'C' has only very -rudimentary abstraction facilities, and support for exception-handling -is completely missing. 'C' extension module writers are required to -manually manage Python reference counts, which is both annoyingly -tedious and extremely error-prone. Traditional extension modules also -tend to contain a great deal of boilerplate code repetition which -makes them difficult to maintain, especially when wrapping an evolving -API. - -These limitations have lead to the development of a variety of wrapping -systems. SWIG_ is probably the most popular package for the -integration of C/C++ and Python. A more recent development is SIP_, -which was specifically designed for interfacing Python with the Qt_ -graphical user interface library. Both SWIG and SIP introduce their -own specialized languages for customizing inter-language bindings. -This has certain advantages, but having to deal with three different -languages (Python, C/C++ and the interface language) also introduces -practical and mental difficulties. The CXX_ package demonstrates an -interesting alternative. It shows that at least some parts of -Python's 'C' API can be wrapped and presented through a much more -user-friendly C++ interface. However, unlike SWIG and SIP, CXX does -not include support for wrapping C++ classes as new Python types. - -The features and goals of Boost.Python_ overlap significantly with -many of these other systems. That said, Boost.Python attempts to -maximize convenience and flexibility without introducing a separate -wrapping language. Instead, it presents the user with a high-level -C++ interface for wrapping C++ classes and functions, managing much of -the complexity behind-the-scenes with static metaprogramming. -Boost.Python also goes beyond the scope of earlier systems by -providing: - -* Support for C++ virtual functions that can be overridden in Python. - -* Comprehensive lifetime management facilities for low-level C++ - pointers and references. - -* Support for organizing extensions as Python packages, - with a central registry for inter-language type conversions. - -* A safe and convenient mechanism for tying into Python's powerful - serialization engine (pickle). - -* Coherence with the rules for handling C++ lvalues and rvalues that - can only come from a deep understanding of both the Python and C++ - type systems. - -The key insight that sparked the development of Boost.Python is that -much of the boilerplate code in traditional extension modules could be -eliminated using C++ compile-time introspection. Each argument of a -wrapped C++ function must be extracted from a Python object using a -procedure that depends on the argument type. Similarly the function's -return type determines how the return value will be converted from C++ -to Python. Of course argument and return types are part of each -function's type, and this is exactly the source from which -Boost.Python deduces most of the information required. - -This approach leads to *user guided wrapping*: as much information is -extracted directly from the source code to be wrapped as is possible -within the framework of pure C++, and some additional information is -supplied explicitly by the user. Mostly the guidance is mechanical -and little real intervention is required. Because the interface -specification is written in the same full-featured language as the -code being exposed, the user has unprecedented power available when -she does need to take control. - -.. _Python: http://www.python.org/ -.. _SWIG: http://www.swig.org/ -.. _SIP: http://www.riverbankcomputing.co.uk/sip/index.php -.. _Qt: http://www.trolltech.com/ -.. _CXX: http://cxx.sourceforge.net/ -.. _Boost.Python: http://www.boost.org/libs/python/doc - -=========================== - Boost.Python Design Goals -=========================== - -The primary goal of Boost.Python is to allow users to expose C++ -classes and functions to Python using nothing more than a C++ -compiler. In broad strokes, the user experience should be one of -directly manipulating C++ objects from Python. - -However, it's also important not to translate all interfaces *too* -literally: the idioms of each language must be respected. For -example, though C++ and Python both have an iterator concept, they are -expressed very differently. Boost.Python has to be able to bridge the -interface gap. - -It must be possible to insulate Python users from crashes resulting -from trivial misuses of C++ interfaces, such as accessing -already-deleted objects. By the same token the library should -insulate C++ users from low-level Python 'C' API, replacing -error-prone 'C' interfaces like manual reference-count management and -raw ``PyObject`` pointers with more-robust alternatives. - -Support for component-based development is crucial, so that C++ types -exposed in one extension module can be passed to functions exposed in -another without loss of crucial information like C++ inheritance -relationships. - -Finally, all wrapping must be *non-intrusive*, without modifying or -even seeing the original C++ source code. Existing C++ libraries have -to be wrappable by third parties who only have access to header files -and binaries. - -========================== - Hello Boost.Python World -========================== - -And now for a preview of Boost.Python, and how it improves on the raw -facilities offered by Python. Here's a function we might want to -expose:: - - char const* greet(unsigned x) - { - static char const* const msgs[] = { "hello", "Boost.Python", "world!" }; - - if (x > 2) - throw std::range_error("greet: index out of range"); - - return msgs[x]; - } - -To wrap this function in standard C++ using the Python 'C' API, we'd -need something like this:: - - extern "C" // all Python interactions use 'C' linkage and calling convention - { - // Wrapper to handle argument/result conversion and checking - PyObject* greet_wrap(PyObject* args, PyObject * keywords) - { - int x; - if (PyArg_ParseTuple(args, "i", &x)) // extract/check arguments - { - char const* result = greet(x); // invoke wrapped function - return PyString_FromString(result); // convert result to Python - } - return 0; // error occurred - } - - // Table of wrapped functions to be exposed by the module - static PyMethodDef methods[] = { - { "greet", greet_wrap, METH_VARARGS, "return one of 3 parts of a greeting" } - , { NULL, NULL, 0, NULL } // sentinel - }; - - // module initialization function - DL_EXPORT init_hello() - { - (void) Py_InitModule("hello", methods); // add the methods to the module - } - } - -Now here's the wrapping code we'd use to expose it with Boost.Python:: - - #include - using namespace boost::python; - BOOST_PYTHON_MODULE(hello) - { - def("greet", greet, "return one of 3 parts of a greeting"); - } - -and here it is in action:: - - >>> import hello - >>> for x in range(3): - ... print hello.greet(x) - ... - hello - Boost.Python - world! - -Aside from the fact that the 'C' API version is much more verbose, -it's worth noting a few things that it doesn't handle correctly: - -* The original function accepts an unsigned integer, and the Python - 'C' API only gives us a way of extracting signed integers. The - Boost.Python version will raise a Python exception if we try to pass - a negative number to ``hello.greet``, but the other one will proceed - to do whatever the C++ implementation does when converting an - negative integer to unsigned (usually wrapping to some very large - number), and pass the incorrect translation on to the wrapped - function. - -* That brings us to the second problem: if the C++ ``greet()`` - function is called with a number greater than 2, it will throw an - exception. Typically, if a C++ exception propagates across the - boundary with code generated by a 'C' compiler, it will cause a - crash. As you can see in the first version, there's no C++ - scaffolding there to prevent this from happening. Functions wrapped - by Boost.Python automatically include an exception-handling layer - which protects Python users by translating unhandled C++ exceptions - into a corresponding Python exception. - -* A slightly more-subtle limitation is that the argument conversion - used in the Python 'C' API case can only get that integer ``x`` in - *one way*. PyArg_ParseTuple can't convert Python ``long`` objects - (arbitrary-precision integers) which happen to fit in an ``unsigned - int`` but not in a ``signed long``, nor will it ever handle a - wrapped C++ class with a user-defined implicit ``operator unsigned - int()`` conversion. Boost.Python's dynamic type conversion - registry allows users to add arbitrary conversion methods. - -================== - Library Overview -================== - -This section outlines some of the library's major features. Except as -neccessary to avoid confusion, details of library implementation are -omitted. - ------------------- - Exposing Classes ------------------- - -C++ classes and structs are exposed with a similarly-terse interface. -Given:: - - struct World - { - void set(std::string msg) { this->msg = msg; } - std::string greet() { return msg; } - std::string msg; - }; - -The following code will expose it in our extension module:: - - #include - BOOST_PYTHON_MODULE(hello) - { - class_("World") - .def("greet", &World::greet) - .def("set", &World::set) - ; - } - -Although this code has a certain pythonic familiarity, people -sometimes find the syntax bit confusing because it doesn't look like -most of the C++ code they're used to. All the same, this is just -standard C++. Because of their flexible syntax and operator -overloading, C++ and Python are great for defining domain-specific -(sub)languages -(DSLs), and that's what we've done in Boost.Python. To break it down:: - - class_("World") - -constructs an unnamed object of type ``class_`` and passes -``"World"`` to its constructor. This creates a new-style Python class -called ``World`` in the extension module, and associates it with the -C++ type ``World`` in the Boost.Python type conversion registry. We -might have also written:: - - class_ w("World"); - -but that would've been more verbose, since we'd have to name ``w`` -again to invoke its ``def()`` member function:: - - w.def("greet", &World::greet) - -There's nothing special about the location of the dot for member -access in the original example: C++ allows any amount of whitespace on -either side of a token, and placing the dot at the beginning of each -line allows us to chain as many successive calls to member functions -as we like with a uniform syntax. The other key fact that allows -chaining is that ``class_<>`` member functions all return a reference -to ``*this``. - -So the example is equivalent to:: - - class_ w("World"); - w.def("greet", &World::greet); - w.def("set", &World::set); - -It's occasionally useful to be able to break down the components of a -Boost.Python class wrapper in this way, but the rest of this article -will stick to the terse syntax. - -For completeness, here's the wrapped class in use: :: - - >>> import hello - >>> planet = hello.World() - >>> planet.set('howdy') - >>> planet.greet() - 'howdy' - -Constructors -============ - -Since our ``World`` class is just a plain ``struct``, it has an -implicit no-argument (nullary) constructor. Boost.Python exposes the -nullary constructor by default, which is why we were able to write: :: - - >>> planet = hello.World() - -However, well-designed classes in any language may require constructor -arguments in order to establish their invariants. Unlike Python, -where ``__init__`` is just a specially-named method, In C++ -constructors cannot be handled like ordinary member functions. In -particular, we can't take their address: ``&World::World`` is an -error. The library provides a different interface for specifying -constructors. Given:: - - struct World - { - World(std::string msg); // added constructor - ... - -we can modify our wrapping code as follows:: - - class_("World", init()) - ... - -of course, a C++ class may have additional constructors, and we can -expose those as well by passing more instances of ``init<...>`` to -``def()``:: - - class_("World", init()) - .def(init()) - ... - -Boost.Python allows wrapped functions, member functions, and -constructors to be overloaded to mirror C++ overloading. - -Data Members and Properties -=========================== - -Any publicly-accessible data members in a C++ class can be easily -exposed as either ``readonly`` or ``readwrite`` attributes:: - - class_("World", init()) - .def_readonly("msg", &World::msg) - ... - -and can be used directly in Python: :: - - >>> planet = hello.World('howdy') - >>> planet.msg - 'howdy' - -This does *not* result in adding attributes to the ``World`` instance -``__dict__``, which can result in substantial memory savings when -wrapping large data structures. In fact, no instance ``__dict__`` -will be created at all unless attributes are explicitly added from -Python. Boost.Python owes this capability to the new Python 2.2 type -system, in particular the descriptor interface and ``property`` type. - -In C++, publicly-accessible data members are considered a sign of poor -design because they break encapsulation, and style guides usually -dictate the use of "getter" and "setter" functions instead. In -Python, however, ``__getattr__``, ``__setattr__``, and since 2.2, -``property`` mean that attribute access is just one more -well-encapsulated syntactic tool at the programmer's disposal. -Boost.Python bridges this idiomatic gap by making Python ``property`` -creation directly available to users. If ``msg`` were private, we -could still expose it as attribute in Python as follows:: - - class_("World", init()) - .add_property("msg", &World::greet, &World::set) - ... - -The example above mirrors the familiar usage of properties in Python -2.2+: :: - - >>> class World(object): - ... __init__(self, msg): - ... self.__msg = msg - ... def greet(self): - ... return self.__msg - ... def set(self, msg): - ... self.__msg = msg - ... msg = property(greet, set) - -Operator Overloading -==================== - -The ability to write arithmetic operators for user-defined types has -been a major factor in the success of both languages for numerical -computation, and the success of packages like NumPy_ attests to the -power of exposing operators in extension modules. Boost.Python -provides a concise mechanism for wrapping operator overloads. The -example below shows a fragment from a wrapper for the Boost rational -number library:: - - class_ >("rational_int") - .def(init()) // constructor, e.g. rational_int(3,4) - .def("numerator", &rational::numerator) - .def("denominator", &rational::denominator) - .def(-self) // __neg__ (unary minus) - .def(self + self) // __add__ (homogeneous) - .def(self * self) // __mul__ - .def(self + int()) // __add__ (heterogenous) - .def(int() + self) // __radd__ - ... - -The magic is performed using a simplified application of "expression -templates" [VELD1995]_, a technique originally developed for -optimization of high-performance matrix algebra expressions. The -essence is that instead of performing the computation immediately, -operators are overloaded to construct a type *representing* the -computation. In matrix algebra, dramatic optimizations are often -available when the structure of an entire expression can be taken into -account, rather than evaluating each operation "greedily". -Boost.Python uses the same technique to build an appropriate Python -method object based on expressions involving ``self``. - -.. _NumPy: http://www.pfdubois.com/numpy/ - -Inheritance -=========== - -C++ inheritance relationships can be represented to Boost.Python by adding -an optional ``bases<...>`` argument to the ``class_<...>`` template -parameter list as follows:: - - class_ >("Derived") - ... - -This has two effects: - -1. When the ``class_<...>`` is created, Python type objects - corresponding to ``Base1`` and ``Base2`` are looked up in - Boost.Python's registry, and are used as bases for the new Python - ``Derived`` type object, so methods exposed for the Python ``Base1`` - and ``Base2`` types are automatically members of the ``Derived`` - type. Because the registry is global, this works correctly even if - ``Derived`` is exposed in a different module from either of its - bases. - -2. C++ conversions from ``Derived`` to its bases are added to the - Boost.Python registry. Thus wrapped C++ methods expecting (a - pointer or reference to) an object of either base type can be - called with an object wrapping a ``Derived`` instance. Wrapped - member functions of class ``T`` are treated as though they have an - implicit first argument of ``T&``, so these conversions are - neccessary to allow the base class methods to be called for derived - objects. - -Of course it's possible to derive new Python classes from wrapped C++ -class instances. Because Boost.Python uses the new-style class -system, that works very much as for the Python built-in types. There -is one significant detail in which it differs: the built-in types -generally establish their invariants in their ``__new__`` function, so -that derived classes do not need to call ``__init__`` on the base -class before invoking its methods : :: - - >>> class L(list): - ... def __init__(self): - ... pass - ... - >>> L().reverse() - >>> - -Because C++ object construction is a one-step operation, C++ instance -data cannot be constructed until the arguments are available, in the -``__init__`` function: :: - - >>> class D(SomeBoostPythonClass): - ... def __init__(self): - ... pass - ... - >>> D().some_boost_python_method() - Traceback (most recent call last): - File "", line 1, in ? - TypeError: bad argument type for built-in operation - -This happened because Boost.Python couldn't find instance data of type -``SomeBoostPythonClass`` within the ``D`` instance; ``D``'s ``__init__`` -function masked construction of the base class. It could be corrected -by either removing ``D``'s ``__init__`` function or having it call -``SomeBoostPythonClass.__init__(...)`` explicitly. - -Virtual Functions -================= - -Deriving new types in Python from extension classes is not very -interesting unless they can be used polymorphically from C++. In -other words, Python method implementations should appear to override -the implementation of C++ virtual functions when called *through base -class pointers/references from C++*. Since the only way to alter the -behavior of a virtual function is to override it in a derived class, -the user must build a special derived class to dispatch a polymorphic -class' virtual functions:: - - // - // interface to wrap: - // - class Base - { - public: - virtual int f(std::string x) { return 42; } - virtual ~Base(); - }; - - int calls_f(Base const& b, std::string x) { return b.f(x); } - - // - // Wrapping Code - // - - // Dispatcher class - struct BaseWrap : Base - { - // Store a pointer to the Python object - BaseWrap(PyObject* self_) : self(self_) {} - PyObject* self; - - // Default implementation, for when f is not overridden - int f_default(std::string x) { return this->Base::f(x); } - // Dispatch implementation - int f(std::string x) { return call_method(self, "f", x); } - }; - - ... - def("calls_f", calls_f); - class_("Base") - .def("f", &Base::f, &BaseWrap::f_default) - ; - -Now here's some Python code which demonstrates: :: - - >>> class Derived(Base): - ... def f(self, s): - ... return len(s) - ... - >>> calls_f(Base(), 'foo') - 42 - >>> calls_f(Derived(), 'forty-two') - 9 - -Things to notice about the dispatcher class: - -* The key element which allows overriding in Python is the - ``call_method`` invocation, which uses the same global type - conversion registry as the C++ function wrapping does to convert its - arguments from C++ to Python and its return type from Python to C++. - -* Any constructor signatures you wish to wrap must be replicated with - an initial ``PyObject*`` argument - -* The dispatcher must store this argument so that it can be used to - invoke ``call_method`` - -* The ``f_default`` member function is needed when the function being - exposed is not pure virtual; there's no other way ``Base::f`` can be - called on an object of type ``BaseWrap``, since it overrides ``f``. - -Deeper Reflection on the Horizon? -================================= - -Admittedly, this formula is tedious to repeat, especially on a project -with many polymorphic classes. That it is neccessary reflects some -limitations in C++'s compile-time introspection capabilities: there's -no way to enumerate the members of a class and find out which are -virtual functions. At least one very promising project has been -started to write a front-end which can generate these dispatchers (and -other wrapping code) automatically from C++ headers. - -Pyste_ is being developed by Bruno da Silva de Oliveira. It builds on -GCC_XML_, which generates an XML version of GCC's internal program -representation. Since GCC is a highly-conformant C++ compiler, this -ensures correct handling of the most-sophisticated template code and -full access to the underlying type system. In keeping with the -Boost.Python philosophy, a Pyste interface description is neither -intrusive on the code being wrapped, nor expressed in some unfamiliar -language: instead it is a 100% pure Python script. If Pyste is -successful it will mark a move away from wrapping everything directly -in C++ for many of our users. It will also allow us the choice to -shift some of the metaprogram code from C++ to Python. We expect that -soon, not only our users but the Boost.Python developers themselves -will be "thinking hybrid" about their own code. - -.. _`GCC_XML`: http://www.gccxml.org/HTML/Index.html -.. _`Pyste`: http://www.boost.org/libs/python/pyste - ---------------- - Serialization ---------------- - -*Serialization* is the process of converting objects in memory to a -form that can be stored on disk or sent over a network connection. The -serialized object (most often a plain string) can be retrieved and -converted back to the original object. A good serialization system will -automatically convert entire object hierarchies. Python's standard -``pickle`` module is just such a system. It leverages the language's strong -runtime introspection facilities for serializing practically arbitrary -user-defined objects. With a few simple and unintrusive provisions this -powerful machinery can be extended to also work for wrapped C++ objects. -Here is an example:: - - #include - - struct World - { - World(std::string a_msg) : msg(a_msg) {} - std::string greet() const { return msg; } - std::string msg; - }; - - #include - using namespace boost::python; - - struct World_picklers : pickle_suite - { - static tuple - getinitargs(World const& w) { return make_tuple(w.greet()); } - }; - - BOOST_PYTHON_MODULE(hello) - { - class_("World", init()) - .def("greet", &World::greet) - .def_pickle(World_picklers()) - ; - } - -Now let's create a ``World`` object and put it to rest on disk:: - - >>> import hello - >>> import pickle - >>> a_world = hello.World("howdy") - >>> pickle.dump(a_world, open("my_world", "w")) - -In a potentially *different script* on a potentially *different -computer* with a potentially *different operating system*:: - - >>> import pickle - >>> resurrected_world = pickle.load(open("my_world", "r")) - >>> resurrected_world.greet() - 'howdy' - -Of course the ``cPickle`` module can also be used for faster -processing. - -Boost.Python's ``pickle_suite`` fully supports the ``pickle`` protocol -defined in the standard Python documentation. Like a __getinitargs__ -function in Python, the pickle_suite's getinitargs() is responsible for -creating the argument tuple that will be use to reconstruct the pickled -object. The other elements of the Python pickling protocol, -__getstate__ and __setstate__ can be optionally provided via C++ -getstate and setstate functions. C++'s static type system allows the -library to ensure at compile-time that nonsensical combinations of -functions (e.g. getstate without setstate) are not used. - -Enabling serialization of more complex C++ objects requires a little -more work than is shown in the example above. Fortunately the -``object`` interface (see next section) greatly helps in keeping the -code manageable. - ------------------- - Object interface ------------------- - -Experienced 'C' language extension module authors will be familiar -with the ubiquitous ``PyObject*``, manual reference-counting, and the -need to remember which API calls return "new" (owned) references or -"borrowed" (raw) references. These constraints are not just -cumbersome but also a major source of errors, especially in the -presence of exceptions. - -Boost.Python provides a class ``object`` which automates reference -counting and provides conversion to Python from C++ objects of -arbitrary type. This significantly reduces the learning effort for -prospective extension module writers. - -Creating an ``object`` from any other type is extremely simple:: - - object s("hello, world"); // s manages a Python string - -``object`` has templated interactions with all other types, with -automatic to-python conversions. It happens so naturally that it's -easily overlooked:: - - object ten_Os = 10 * s[4]; // -> "oooooooooo" - -In the example above, ``4`` and ``10`` are converted to Python objects -before the indexing and multiplication operations are invoked. - -The ``extract`` class template can be used to convert Python objects -to C++ types:: - - double x = extract(o); - -If a conversion in either direction cannot be performed, an -appropriate exception is thrown at runtime. - -The ``object`` type is accompanied by a set of derived types -that mirror the Python built-in types such as ``list``, ``dict``, -``tuple``, etc. as much as possible. This enables convenient -manipulation of these high-level types from C++:: - - dict d; - d["some"] = "thing"; - d["lucky_number"] = 13; - list l = d.keys(); - -This almost looks and works like regular Python code, but it is pure -C++. Of course we can wrap C++ functions which accept or return -``object`` instances. - -================= - Thinking hybrid -================= - -Because of the practical and mental difficulties of combining -programming languages, it is common to settle a single language at the -outset of any development effort. For many applications, performance -considerations dictate the use of a compiled language for the core -algorithms. Unfortunately, due to the complexity of the static type -system, the price we pay for runtime performance is often a -significant increase in development time. Experience shows that -writing maintainable C++ code usually takes longer and requires *far* -more hard-earned working experience than developing comparable Python -code. Even when developers are comfortable working exclusively in -compiled languages, they often augment their systems by some type of -ad hoc scripting layer for the benefit of their users without ever -availing themselves of the same advantages. - -Boost.Python enables us to *think hybrid*. Python can be used for -rapidly prototyping a new application; its ease of use and the large -pool of standard libraries give us a head start on the way to a -working system. If necessary, the working code can be used to -discover rate-limiting hotspots. To maximize performance these can -be reimplemented in C++, together with the Boost.Python bindings -needed to tie them back into the existing higher-level procedure. - -Of course, this *top-down* approach is less attractive if it is clear -from the start that many algorithms will eventually have to be -implemented in C++. Fortunately Boost.Python also enables us to -pursue a *bottom-up* approach. We have used this approach very -successfully in the development of a toolbox for scientific -applications. The toolbox started out mainly as a library of C++ -classes with Boost.Python bindings, and for a while the growth was -mainly concentrated on the C++ parts. However, as the toolbox is -becoming more complete, more and more newly added functionality can be -implemented in Python. - -.. image:: python_cpp_mix.jpg - -This figure shows the estimated ratio of newly added C++ and Python -code over time as new algorithms are implemented. We expect this -ratio to level out near 70% Python. Being able to solve new problems -mostly in Python rather than a more difficult statically typed -language is the return on our investment in Boost.Python. The ability -to access all of our code from Python allows a broader group of -developers to use it in the rapid development of new applications. - -===================== - Development history -===================== - -The first version of Boost.Python was developed in 2000 by Dave -Abrahams at Dragon Systems, where he was privileged to have Tim Peters -as a guide to "The Zen of Python". One of Dave's jobs was to develop -a Python-based natural language processing system. Since it was -eventually going to be targeting embedded hardware, it was always -assumed that the compute-intensive core would be rewritten in C++ to -optimize speed and memory footprint [#proto]_. The project also wanted to -test all of its C++ code using Python test scripts [#test]_. The only -tool we knew of for binding C++ and Python was SWIG_, and at the time -its handling of C++ was weak. It would be false to claim any deep -insight into the possible advantages of Boost.Python's approach at -this point. Dave's interest and expertise in fancy C++ template -tricks had just reached the point where he could do some real damage, -and Boost.Python emerged as it did because it filled a need and -because it seemed like a cool thing to try. - -This early version was aimed at many of the same basic goals we've -described in this paper, differing most-noticeably by having a -slightly more cumbersome syntax and by lack of special support for -operator overloading, pickling, and component-based development. -These last three features were quickly added by Ullrich Koethe and -Ralf Grosse-Kunstleve [#feature]_, and other enthusiastic contributors arrived -on the scene to contribute enhancements like support for nested -modules and static member functions. - -By early 2001 development had stabilized and few new features were -being added, however a disturbing new fact came to light: Ralf had -begun testing Boost.Python on pre-release versions of a compiler using -the EDG_ front-end, and the mechanism at the core of Boost.Python -responsible for handling conversions between Python and C++ types was -failing to compile. As it turned out, we had been exploiting a very -common bug in the implementation of all the C++ compilers we had -tested. We knew that as C++ compilers rapidly became more -standards-compliant, the library would begin failing on more -platforms. Unfortunately, because the mechanism was so central to the -functioning of the library, fixing the problem looked very difficult. - -Fortunately, later that year Lawrence Berkeley and later Lawrence -Livermore National labs contracted with `Boost Consulting`_ for support -and development of Boost.Python, and there was a new opportunity to -address fundamental issues and ensure a future for the library. A -redesign effort began with the low level type conversion architecture, -building in standards-compliance and support for component-based -development (in contrast to version 1 where conversions had to be -explicitly imported and exported across module boundaries). A new -analysis of the relationship between the Python and C++ objects was -done, resulting in more intuitive handling for C++ lvalues and -rvalues. - -The emergence of a powerful new type system in Python 2.2 made the -choice of whether to maintain compatibility with Python 1.5.2 easy: -the opportunity to throw away a great deal of elaborate code for -emulating classic Python classes alone was too good to pass up. In -addition, Python iterators and descriptors provided crucial and -elegant tools for representing similar C++ constructs. The -development of the generalized ``object`` interface allowed us to -further shield C++ programmers from the dangers and syntactic burdens -of the Python 'C' API. A great number of other features including C++ -exception translation, improved support for overloaded functions, and -most significantly, CallPolicies for handling pointers and -references, were added during this period. - -In October 2002, version 2 of Boost.Python was released. Development -since then has concentrated on improved support for C++ runtime -polymorphism and smart pointers. Peter Dimov's ingenious -``boost::shared_ptr`` design in particular has allowed us to give the -hybrid developer a consistent interface for moving objects back and -forth across the language barrier without loss of information. At -first, we were concerned that the sophistication and complexity of the -Boost.Python v2 implementation might discourage contributors, but the -emergence of Pyste_ and several other significant feature -contributions have laid those fears to rest. Daily questions on the -Python C++-sig and a backlog of desired improvements show that the -library is getting used. To us, the future looks bright. - -.. _`EDG`: http://www.edg.com - -============= - Conclusions -============= - -Boost.Python achieves seamless interoperability between two rich and -complimentary language environments. Because it leverages template -metaprogramming to introspect about types and functions, the user -never has to learn a third syntax: the interface definitions are -written in concise and maintainable C++. Also, the wrapping system -doesn't have to parse C++ headers or represent the type system: the -compiler does that work for us. - -Computationally intensive tasks play to the strengths of C++ and are -often impossible to implement efficiently in pure Python, while jobs -like serialization that are trivial in Python can be very difficult in -pure C++. Given the luxury of building a hybrid software system from -the ground up, we can approach design with new confidence and power. - -=========== - Citations -=========== - -.. [VELD1995] T. Veldhuizen, "Expression Templates," C++ Report, - Vol. 7 No. 5 June 1995, pp. 26-31. - http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html - -=========== - Footnotes -=========== - -.. [#proto] In retrospect, it seems that "thinking hybrid" from the - ground up might have been better for the NLP system: the - natural component boundaries defined by the pure python - prototype turned out to be inappropriate for getting the - desired performance and memory footprint out of the C++ core, - which eventually caused some redesign overhead on the Python - side when the core was moved to C++. - -.. [#test] We also have some reservations about driving all C++ - testing through a Python interface, unless that's the only way - it will be ultimately used. Any transition across language - boundaries with such different object models can inevitably - mask bugs. - -.. [#feature] These features were expressed very differently in v1 of - Boost.Python +This file has been moved to http://www.boost-consulting.com/writing/bpl.txt. diff --git a/doc/news.html b/doc/news.html index ce8eb911..cabd323e 100644 --- a/doc/news.html +++ b/doc/news.html @@ -29,13 +29,24 @@
-
8 Sept 2004
+
19 November 2004 - 1.32 release
    +
  • Updated to use the Boost Software License.
  • +
  • A new, better method of wrapping classes with virtual functions has been implemented.
  • +
  • Support for upcoming GCC symbol export control features have been folded in, thanks to Niall Douglas.
  • +
  • Improved support for std::auto_ptr-like types.
  • +
  • The Visual C++ bug that makes top-level cv-qualification of function parameter types part of the function type has been worked around.
  • +
  • Components used by other libraries have been moved out of python/detail and into boost/detail to improve dependency relationships.
  • +
  • Miscellaneous bug fixes and compiler workarounds.
  • +
+
+
8 Sept 2004
+ +
Support for Python's Bool type, thanks to Daniel Holth. -
11 Sept 2003
@@ -189,12 +200,12 @@ BOOST_PYTHON_MODULE(test)

Revised - 11 September 2003 + 19 November 2004

© Copyright Dave - Abrahams 2002-2003. All Rights Reserved.

+ Abrahams 2002-2003.

diff --git a/doc/tutorial/boost.png b/doc/tutorial/boost.png new file mode 100644 index 00000000..c5187911 Binary files /dev/null and b/doc/tutorial/boost.png differ diff --git a/doc/tutorial/doc/Jamfile.v2 b/doc/tutorial/doc/Jamfile.v2 index 16442294..f8e6676c 100644 --- a/doc/tutorial/doc/Jamfile.v2 +++ b/doc/tutorial/doc/Jamfile.v2 @@ -1,5 +1,5 @@ project boost/libs/python/doc/tutorial/doc ; import boostbook : boostbook ; -boostbook tutorial : tutorial.qbk.xml +boostbook tutorial : tutorial.xml ; diff --git a/doc/tutorial/doc/html/HTML.manifest b/doc/tutorial/doc/html/HTML.manifest index f2bb5aec..41fd08be 100644 --- a/doc/tutorial/doc/html/HTML.manifest +++ b/doc/tutorial/doc/html/HTML.manifest @@ -1,9 +1,9 @@ index.html -boost_python.hello.html -boost_python.exposing.html -boost_python.functions.html -boost_python.object.html -boost_python.embedding.html -boost_python.iterators.html -boost_python.exception.html -boost_python.techniques.html +python/hello.html +python/exposing.html +python/functions.html +python/object.html +python/embedding.html +python/iterators.html +python/exception.html +python/techniques.html diff --git a/doc/tutorial/doc/html/boost_python.embedding.html b/doc/tutorial/doc/html/boost_python.embedding.html deleted file mode 100644 index 798dd3c9..00000000 --- a/doc/tutorial/doc/html/boost_python.embedding.html +++ /dev/null @@ -1,238 +0,0 @@ -Embedding
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

Embedding

-By now you should know how to use Boost.Python to call your C++ code from -Python. However, sometimes you may need to do the reverse: call Python code -from the C++-side. This requires you to embed the Python interpreter -into your C++ program.

-Currently, Boost.Python does not directly support everything you'll need -when embedding. Therefore you'll need to use the - -Python/C API to fill in -the gaps. However, Boost.Python already makes embedding a lot easier and, -in a future version, it may become unnecessary to touch the Python/C API at -all. So stay tuned...

Building embedded programs

-To be able to use embedding in your programs, they have to be linked to -both Boost.Python's and Python's static link library.

-Boost.Python's static link library comes in two variants. Both are located -in Boost's /libs/python/build/bin-stage subdirectory. On Windows, the -variants are called boost_python.lib (for release builds) and -boost_python_debug.lib (for debugging). If you can't find the libraries, -you probably haven't built Boost.Python yet. See -and Testing on how to do this.

-Python's static link library can be found in the /libs subdirectory of -your Python directory. On Windows it is called pythonXY.lib where X.Y is -your major Python version number.

-Additionally, Python's /include subdirectory has to be added to your -include path.

-In a Jamfile, all the above boils down to:

    projectroot c:\projects\embedded_program ; # location of the program

-[pre - projectroot c:\projects\embedded_program ; # location of the program


-# bring in the rules for python
-    SEARCH on python.jam =#(BOOST_BUILD_PATH) ;
-    include python.jam ;
-
-    exe embedded_program# name of the executable
-      : #sources
-         embedded_program.cpp
-      :# requirements
-         <find-library>boost_python <library-path>c:\boost\libs\python#(PYTHON_PROPERTIES)
-        <library-path>#(PYTHON_LIB_PATH)
-        <find-library>#(PYTHON_EMBEDDED_LIBRARY) ;

- # bring in the rules for python - SEARCH on python.jam = $(BOOST_BUILD_PATH) ; - include python.jam ;

    exe embedded_program# name of the executable
-      : #sources
-         embedded_program.cpp
-      :# requirements
-         <find-library>boost_python <library-path>c:\boost\libs\python#(PYTHON_PROPERTIES)
-        <library-path>#(PYTHON_LIB_PATH)
-        <find-library>#(PYTHON_EMBEDDED_LIBRARY) ;

- exe embedded_program # name of the executable - : #sources - embedded_program.cpp - : # requirements - <find-library>boost_python <library-path>c:\boost\libs\python - $(PYTHON_PROPERTIES) - <library-path>$(PYTHON_LIB_PATH) - <find-library>$(PYTHON_EMBEDDED_LIBRARY) ; -]

Getting started

-Being able to build is nice, but there is nothing to build yet. Embedding -the Python interpreter into one of your C++ programs requires these 4 -steps:

  1. -#include <boost/python.hpp>

  2. -Call -Py_Initialize() to start the interpreter and create the __main__ module.

  3. -Call other Python C API routines to use the interpreter.

  4. -Call -Py_Finalize() to stop the interpreter and release its resources. -

-(Of course, there can be other C++ code between all of these steps.)

Now that we can embed the interpreter in our programs, lets see how to put it to use...

Using the interpreter

-As you probably already know, objects in Python are reference-counted. -Naturally, the PyObjects of the Python/C API are also reference-counted. -There is a difference however. While the reference-counting is fully -automatic in Python, the Python/C API requires you to do it - -by hand. This is -messy and especially hard to get right in the presence of C++ exceptions. -Fortunately Boost.Python provides the -handle and - -object class templates to automate the process.

Reference-counting handles and objects

-There are two ways in which a function in the Python/C API can return a -PyObject*: as a borrowed reference or as a new reference. Which of -these a function uses, is listed in that function's documentation. The two -require slightely different approaches to reference-counting but both can -be 'handled' by Boost.Python.

-For a function returning a borrowed reference we'll have to tell the -handle that the PyObject* is borrowed with the aptly named - -borrowed function. Two functions -returning borrowed references are -PyImport_AddModule and -PyModule_GetDict. -The former returns a reference to an already imported module, the latter -retrieves a module's namespace dictionary. Let's use them to retrieve the -namespace of the __main__ module:

    object main_module((
-        handle<>(borrowed(PyImport_AddModule("__main__")))));
-
-    object main_namespace = main_module.attr("__dict__");

-For a function returning a new reference we can just create a handle -out of the raw PyObject* without wrapping it in a call to borrowed. One -such function that returns a new reference is -PyRun_String which we'll -discuss in the next section.

Handle is a class template, so why haven't we been using any template parameters?

handle has a single template parameter specifying the type of the managed object. This type is PyObject 99% of the time, so the parameter was defaulted to PyObject for convenience. Therefore we can use the shorthand handle<> instead of the longer, but equivalent, handle<PyObject>. -

Running Python code

-To run Python code from C++ there is a family of functions in the API -starting with the PyRun prefix. You can find the full list of these -functions -here. They -all work similarly so we will look at only one of them, namely:

    PyObject* PyRun_String(char *str, int start, PyObject *globals, PyObject *locals)

-PyRun_String takes the code to execute as a null-terminated (C-style) -string in its str parameter. The function returns a new reference to a -Python object. Which object is returned depends on the start paramater.

-The start parameter is the start symbol from the Python grammar to use -for interpreting the code. The possible values are:

Start symbols

-Py_eval_inputfor interpreting isolated expressions
-Py_file_inputfor interpreting sequences of statements
-Py_single_inputfor interpreting a single statement

-When using -Py_eval_input, the input string must contain a single expression -and its result is returned. When using -Py_file_input, the string can -contain an abitrary number of statements and None is returned. - -Py_single_input works in the same way as -Py_file_input but only accepts a -single statement.

-Lastly, the globals and locals parameters are Python dictionaries -containing the globals and locals of the context in which to run the code. -For most intents and purposes you can use the namespace dictionary of the -__main__ module for both parameters.

-We have already seen how to get the __main__ module's namespace so let's -run some Python code in it:

    object main_module((
-        handle<>(borrowed(PyImport_AddModule("__main__")))));
-
-    object main_namespace = main_module.attr("__dict__");
-
-    handle<> ignored((PyRun_String(
-
-        "hello = file('hello.txt', 'w')\n"
-        "hello.write('Hello world!')\n"
-        "hello.close()"
-
-      , Py_file_input
-      , main_namespace.ptr()
-      , main_namespace.ptr())
-    ));

-Because the Python/C API doesn't know anything about objects, we used -the object's ptr member function to retrieve the PyObject*.

-This should create a file called 'hello.txt' in the current directory -containing a phrase that is well-known in programming circles.

Note that we wrap the return value of -PyRun_String in a -(nameless) handle even though we are not interested in it. If we didn't -do this, the the returned object would be kept alive unnecessarily. Unless -you want to be a Dr. Frankenstein, always wrap PyObject*s in handles.

Beyond handles

-It's nice that handle manages the reference counting details for us, but -other than that it doesn't do much. Often we'd like to have a more useful -class to manipulate Python objects. But we have already seen such a class -above, and in the -previous section: the aptly -named object class and it's derivatives. We've already seen that they -can be constructed from a handle. The following examples should further -illustrate this fact:

    object main_module((
-         handle<>(borrowed(PyImport_AddModule("__main__")))));
-
-    object main_namespace = main_module.attr("__dict__");
-
-    handle<> ignored((PyRun_String(
-
-        "result = 5 ** 2"
-
-        , Py_file_input
-        , main_namespace.ptr()
-        , main_namespace.ptr())
-    ));
-
-    int five_squared = extract<int>(main_namespace["result"]);

-Here we create a dictionary object for the __main__ module's namespace. -Then we assign 5 squared to the result variable and read this variable from -the dictionary. Another way to achieve the same result is to let - -PyRun_String return the result directly with -Py_eval_input:

    object result((handle<>(
-        PyRun_String("5 ** 2"
-            , Py_eval_input
-            , main_namespace.ptr()
-            , main_namespace.ptr()))
-    ));
-
-    int five_squared = extract<int>(result);

Note that object's member function to return the wrapped -PyObject* is called ptr instead of get. This makes sense if you -take into account the different functions that object and handle -perform.

Exception handling

-If an exception occurs in the execution of some Python code, the -PyRun_String function returns a null pointer. Constructing a handle out of this null pointer throws -error_already_set, so basically, the Python exception is automatically translated into a C++ exception when using handle:

    try
-    {
-        object result((handle<>(PyRun_String(
-            "5/0"
-          , Py_eval_input
-          , main_namespace.ptr()
-          , main_namespace.ptr()))
-        ));
-
-        // execution will never get here:
-        int five_divided_by_zero = extract<int>(result);
-    }
-    catch(error_already_set)
-    {
-        // handle the exception in some way
-    }

-The error_already_set exception class doesn't carry any information in itself. To find out more about the Python exception that occurred, you need to use the -exception handling functions of the Python/C API in your catch-statement. This can be as simple as calling -PyErr_Print() to print the exception's traceback to the console, or comparing the type of the exception with those of the -standard exceptions:

    catch(error_already_set)
-    {
-        if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
-        {
-            // handle ZeroDivisionError specially
-        }
-        else
-        {
-            // print all other errors to stderr
-            PyErr_Print();
-        }
-    }

-(To retrieve even more information from the exception you can use some of the other exception handling functions listed -here.)

-If you'd rather not have handle throw a C++ exception when it is constructed, you can use the -allow_null function in the same way you'd use borrowed:

    handle<> result((allow_null(PyRun_String(
-        "5/0"
-       , Py_eval_input
-       , main_namespace.ptr()
-       , main_namespace.ptr()))));
-
-    if (!result)
-        // Python exception occurred
-    else
-        // everything went okay, it's safe to use the result
-
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.exception.html b/doc/tutorial/doc/html/boost_python.exception.html deleted file mode 100644 index 62744cbb..00000000 --- a/doc/tutorial/doc/html/boost_python.exception.html +++ /dev/null @@ -1,13 +0,0 @@ - Exception Translation
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

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 const& x) {
-        PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave...");
-    }
-    BOOST_PYTHON_MODULE(kubrick) {
-         register_exception_translator<
-              PodBayDoorException>(translator);
-         ...
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.exposing.html b/doc/tutorial/doc/html/boost_python.exposing.html deleted file mode 100644 index e312a112..00000000 --- a/doc/tutorial/doc/html/boost_python.exposing.html +++ /dev/null @@ -1,326 +0,0 @@ - Exposing Classes
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

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'

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.

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, assuming we have placed our Var class inside the namespace -hello as we did before:

    >>> x = hello.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
-

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", &Num::get)
-        .add_property("value", &Num::get, &Num::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", &Num::get)

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:

  1. -Derived automatically inherits all of Base's Python methods (wrapped C++ member functions) -
  2. 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<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>());

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;
-    };
-
-
-    struct BaseWrap : Base
-    {
-        BaseWrap(PyObject* self_)
-            : self(self_) {}
-        BaseWrap(PyObject* self_, Base const& copy)
-            : Base(copy), self(self_) {}
-        int f() { return call_method<int>(self, "f"); }
-        int default_f() { return Base::f(); } // <<=== ***ADDED***
-        PyObject* self;
-    };
member function and methods

- 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.

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 fromPython. 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 fromC++.]

-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()
-    RuntimeError: This class cannot be instantiated from Python

-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.

Deriving a Python Class

-Continuing, we can derive from our base class Base in Python and override -the virtual function in Python. Before we can do that, we have to set up -our class_ wrapper as:

    class_<Base, BaseWrap, boost::noncopyable>("Base")
-        ;

-Otherwise, we have to suppress the Base class' no_init by adding an -__init__() method to all our derived classes. no_init actually adds -an __init__ method that raises a Python RuntimeError exception.

    >>> 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:

  1. call_f(derived) is called in Python -
  2. -This corresponds to def("call_f", call_f);. Boost.Python dispatches this call. -
  3. int call_f(Base& b) { return b.f(); } accepts the call. -
  4. -The overridden virtual function f of BaseWrap is called. -
  5. call_method<int>(self, "f"); dispatches the call back to Python. -
  6. def f(self): return 42 is finally called. -

Virtual Functions with Default Implementations

-Recall that in the -previous section, we -wrapped a class with a pure virtual function that we then implemented in -C++ or Python classes derived from it. Our base class:

    struct Base
-    {
-        virtual int f() = 0;
-    };

-had a pure virtual function f. If, however, its member function f was -not declared as pure virtual:

    struct Base
-    {
-        virtual int f() { return 0; }
-    };

-and instead had a default implementation that returns 0, as shown above, -we need to add a forwarding function that calls the Base default virtual -function f implementation:

    struct BaseWrap : Base
-    {
-        BaseWrap(PyObject* self_)
-            : self(self_) {}
-        int f() { return call_method<int>(self, "f"); }
-        int default_f() { return Base::f(); } // <<=== ***ADDED***
-        PyObject* self;
-    };

-Then, Boost.Python needs to keep track of 1) the dispatch function f and -2) the forwarding function to its default implementation default_f. -There's a special def function for this purpose. Here's how it is -applied to our example above:

    class_<Base, BaseWrap, BaseWrap, boost::noncopyable>("Base")
-        .def("f", &Base::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

Class Operators/Special Functions

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>("FilePos")
-        .def(self + int())          // __add__
-        .def(int() + self)          // __radd__
-        .def(self - self)           // __sub__
-        .def(self - int())          // __sub__
-        .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".

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_<Rational>()
-        .def(float_(self))                  // __float__
-        .def(pow(self, other<Rational>))    // __pow__
-        .def(abs(self))                     // __abs__
-        .def(str(self))                     // __str__
-        ;

-Need we say more?

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)).
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.functions.html b/doc/tutorial/doc/html/boost_python.functions.html deleted file mode 100644 index 197d884a..00000000 --- a/doc/tutorial/doc/html/boost_python.functions.html +++ /dev/null @@ -1,276 +0,0 @@ -Functions
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

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!
-

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:

  1. f is called passing in a reference to y and a pointer to z
  2. -A reference to y.x is returned -
  3. y is deleted. x is a dangling reference -
  4. x.some_method() is called -
  5. 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:

  1. f is called passing in a reference to y and a pointer to z
  2. -A pointer to z is held by y
  3. -A reference to y.x is returned -
  4. z is deleted. y.z is a dangling pointer -
  5. y.z_value() is called -
  6. z->value() is called -
  7. BOOM!

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<args...,
-        policy2<args...,
-            policy3<args...> > >

-Here is the list of predefined call policies. A complete reference detailing -these can be found -here.

  • with_custodian_and_ward

    - Ties lifetimes of the arguments -
  • with_custodian_and_ward_postcall

    - Ties lifetimes of the arguments and results -
  • return_internal_reference

    - Ties lifetime of one argument to that of result -
  • return_value_policy<T> with T one of:

  • reference_existing_object

    -naive (dangerous) approach -
  • copy_const_reference

    -Boost.Python v1 approach -
  • copy_non_const_reference

  • manage_new_object

    - Adopt a pointer and hold the instance -
Remember the Zen, Luke:

- -"Explicit is better than implicit"

- -"In the face of ambiguity, refuse the temptation to guess"

Overloading

-The following illustrates a scheme for manually wrapping an overloaded -member functions. Of course, the same technique can be applied to wrapping -overloaded non-member functions.

-We have here our C++ class:

    struct X
-    {
-        bool f(int a)
-        {
-            return true;
-        }
-
-        bool f(int a, double b)
-        {
-            return true;
-        }
-
-        bool f(int a, double b, char c)
-        {
-            return true;
-        }
-
-        int f(int a, int b, int c)
-        {
-            return a + b + c;
-        };
-    };

-Class X has 4 overloaded functions. We shall start by introducing some -member function pointer variables:

    bool    (X::*fx1)(int)              = &X::f;
-    bool    (X::*fx2)(int, double)      = &X::f;
-    bool    (X::*fx3)(int, double, char)= &X::f;
-    int     (X::*fx4)(int, int, int)    = &X::f;

-With these in hand, we can proceed to define and wrap this for Python:

    .def("f", fx1)
-    .def("f", fx2)
-    .def("f", fx3)
-    .def("f", fx4)

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, we had to resort to manual -wrapping as outlined in the -previous section, or -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_FUNCTION_OVERLOADS

-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());

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS

-Objects here, objects there, objects here there everywhere. More frequently -than anything else, we need to expose member functions of our classes to -Python. Then again, we have the same inconveniences as before when default -arguments or overloads with a common sequence of initial arguments come -into play. Another macro is provided to make this a breeze.

-Like BOOST_PYTHON_FUNCTION_OVERLOADS, -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS may be used to automatically create -the thin wrappers for wrapping member functions. Let's have an example:

    struct george
-    {
-        void
-        wack_em(int a, int b = 0, char c = 'x')
-        {
-            /*...*/
-        }
-    };

-The macro invocation:

    BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(george_overloads, wack_em, 1, 3)

-will generate a set of thin wrappers for george's wack_em member function -accepting a minimum of 1 and a maximum of 3 arguments (i.e. the third and -fourth macro argument). The thin wrappers are all enclosed in a class named -george_overloads that can then be used as an argument to def(...):

    .def("wack_em", &george::wack_em, george_overloads());

-See the -overloads reference -for details.

init and optional

-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).

Auto-Overloading

-It was mentioned in passing in the previous section that -BOOST_PYTHON_FUNCTION_OVERLOADS and BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS -can also be used for overloaded functions and member functions with a -common sequence of initial arguments. Here is an example:

     void foo()
-     {
-        /*...*/
-     }
-
-     void foo(bool a)
-     {
-        /*...*/
-     }
-
-     void foo(bool a, int b)
-     {
-        /*...*/
-     }
-
-     void foo(bool a, int b, char c)
-     {
-        /*...*/
-     }

-Like in the previous section, we can generate thin wrappers for these -overloaded functions in one-shot:

    BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)

-Then...

    .def("foo", foo, foo_overloads());

-Notice though that we have a situation now where we have a minimum of zero -(0) arguments and a maximum of 3 arguments.

Manual Wrapping

-It is important to emphasize however that the overloaded functions must -have a common sequence of initial arguments. Otherwise, our scheme above -will not work. If this is not the case, we have to wrap our functions - -manually.

-Actually, we can mix and match manual wrapping of overloaded functions and -automatic wrapping through BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS and -its sister, BOOST_PYTHON_FUNCTION_OVERLOADS. Following up on our example -presented in the section -on overloading, since the -first 4 overload functins have a common sequence of initial arguments, we -can use BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS to automatically wrap the -first three of the defs and manually wrap just the last. Here's -how we'll do this:

    BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xf_overloads, f, 1, 4)

-Create a member function pointers as above for both X::f overloads:

    bool    (X::*fx1)(int, double, char)    = &X::f;
-    int     (X::*fx2)(int, int, int)        = &X::f;

-Then...

    .def("f", fx1, xf_overloads());
-    .def("f", fx2)
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.hello.html b/doc/tutorial/doc/html/boost_python.hello.html deleted file mode 100644 index 69b0de88..00000000 --- a/doc/tutorial/doc/html/boost_python.hello.html +++ /dev/null @@ -1,130 +0,0 @@ - Building Hello World
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

Building Hello World

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.

Building without bjam

-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.

-Take note however that the preferred build tool for Boost.Python is bjam. -There are so many ways to set up the build incorrectly. Experience shows -that 90% of the "I can't build Boost.Python" problems come from people -who had to use a different tool. -]

-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:

  • -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 -here. -The complete list of bjam pre-built -executables can be found -here.

Let's Jam!

-Here is our minimalist Jamfile:

    subproject libs/python/example/tutorial ;

-[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
-        <dll>../../build/boost_python# dependencies
-        ;

- 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
-        ;

- 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
-        ;
-

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. 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:

  • -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!

Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.iterators.html b/doc/tutorial/doc/html/boost_python.iterators.html deleted file mode 100644 index d4957d87..00000000 --- a/doc/tutorial/doc/html/boost_python.iterators.html +++ /dev/null @@ -1,53 +0,0 @@ -Iterators
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

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<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:

  • -range(start, finish) -
  • -range<Policies,Target>(start, finish) -

-Here, start/finish may be one of:

  • -member data pointers -
  • -member function pointers -
  • -adaptable function object (use Target parameter) -

iterator

  • -iterator<T, Policies>() -

-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-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.object.html b/doc/tutorial/doc/html/boost_python.object.html deleted file mode 100644 index d737d071..00000000 --- a/doc/tutorial/doc/html/boost_python.object.html +++ /dev/null @@ -1,135 +0,0 @@ - Object Interface
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHomeNext

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++ objects are as close as possible to Python. This -should minimize the learning curve significantly.

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, 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.

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 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

class_<T> as objects

-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);

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 objecto. The second line attempts to extract the -Vec2 object from held by the Boost.Python objecto.

-Take note that we said "attempt to" above. What if the Boost.Python -objecto 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 the mutable copying problem:

    dict d = extract<dict>(x.attr("__dict__"));
-    d['whatever'] = 3;# modifies x.__dict__ !

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>("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.

what is a scope?

- 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 -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>("X")
-                    .def( ... )
-                    .def( ... )
-                ;
-
-    // Expose X::nested as X.nested
-    enum_<X::nested>("nested")
-        .value("red", red)
-        .value("blue", blue)
-        ;
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHomeNext
diff --git a/doc/tutorial/doc/html/boost_python.techniques.html b/doc/tutorial/doc/html/boost_python.techniques.html deleted file mode 100644 index cdf0b885..00000000 --- a/doc/tutorial/doc/html/boost_python.techniques.html +++ /dev/null @@ -1,227 +0,0 @@ - General Techniques
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

PrevUpHome

General Techniques

-Here are presented some useful techniques that you can use while wrapping code with Boost.Python.

Creating Packages

-A Python package is a collection of modules that provide to the user a certain -functionality. If you're not familiar on how to create packages, a good -introduction to them is provided in the - -Python Tutorial.

-But we are wrapping C++ code, using Boost.Python. How can we provide a nice -package interface to our users? To better explain some concepts, let's work -with an example.

-We have a C++ library that works with sounds: reading and writing various -formats, applying filters to the sound data, etc. It is named (conveniently) -sounds. Our library already has a neat C++ namespace hierarchy, like so:

    sounds::core
-    sounds::io
-    sounds::filters

-We would like to present this same hierarchy to the Python user, allowing him -to write code like this:

    import sounds.filters
-    sounds.filters.echo(...)# echo is a C++ function

-The first step is to write the wrapping code. We have to export each module -separately with Boost.Python, like this:

    /* file core.cpp */
-    BOOST_PYTHON_MODULE(core)
-    {
-        /* export everything in the sounds::core namespace */
-        ...
-    }
-
-    /* file io.cpp */
-    BOOST_PYTHON_MODULE(io)
-    {
-        /* export everything in the sounds::io namespace */
-        ...
-    }
-
-    /* file filters.cpp */
-    BOOST_PYTHON_MODULE(filters)
-    {
-        /* export everything in the sounds::filters namespace */
-        ...
-    }

-Compiling these files will generate the following Python extensions: -core.pyd, io.pyd and filters.pyd.

The extension .pyd is used for python extension modules, which -are just shared libraries. Using the default for your system, like .so for -Unix and .dll for Windows, works just as well.

-Now, we create this directory structure for our Python package:

    sounds/
-        __init__.py
-        core.pyd
-        filters.pyd
-        io.pyd
-

-The file __init__.py is what tells Python that the directory sounds/ is -actually a Python package. It can be a empty file, but can also perform some -magic, that will be shown later.

-Now our package is ready. All the user has to do is put sounds into his - -PYTHONPATH and fire up the interpreter:

    >>> import sounds.io
-    >>> import sounds.filters
-    >>> sound = sounds.io.open('file.mp3')
-    >>> new_sound = sounds.filters.echo(sound, 1.0)

-Nice heh?

-This is the simplest way to create hierarchies of packages, but it is not very -flexible. What if we want to add a pure Python function to the filters -package, for instance, one that applies 3 filters in a sound object at once? -Sure, you can do this in C++ and export it, but why not do so in Python? You -don't have to recompile the extension modules, plus it will be easier to write -it.

-If we want this flexibility, we will have to complicate our package hierarchy a -little. First, we will have to change the name of the extension modules:

    /* file core.cpp */
-    BOOST_PYTHON_MODULE(_core)
-    {
-        ...
-        /* export everything in the sounds::core namespace */
-    }

-Note that we added an underscore to the module name. The filename will have to -be changed to _core.pyd as well, and we do the same to the other extension modules. -Now, we change our package hierarchy like so:

    sounds/
-        __init__.py
-        core/
-            __init__.py
-            _core.pyd
-        filters/
-            __init__.py
-            _filters.pyd
-        io/
-            __init__.py
-            _io.pyd
-

-Note that we created a directory for each extension module, and added a -__init__.py to each one. But if we leave it that way, the user will have to -access the functions in the core module with this syntax:

    >>> import sounds.core._core
-    >>> sounds.core._core.foo(...)

-which is not what we want. But here enters the __init__.py magic: everything -that is brought to the __init__.py namespace can be accessed directly by the -user. So, all we have to do is bring the entire namespace from _core.pyd -to core/__init__.py. So add this line of code to sounds/core/__init__.py:

    from _core import *

-We do the same for the other packages. Now the user accesses the functions and -classes in the extension modules like before:

    >>> import sounds.filters
-    >>> sounds.filters.echo(...)

-with the additional benefit that we can easily add pure Python functions to -any module, in a way that the user can't tell the difference between a C++ -function and a Python function. Let's add a pure Python function, -echo_noise, to the filters package. This function applies both the -echo and noise filters in sequence in the given sound object. We -create a file named sounds/filters/echo_noise.py and code our function:

    import _filters
-    def echo_noise(sound):
-        s = _filters.echo(sound)
-        s = _filters.noise(sound)
-        return s

-Next, we add this line to sounds/filters/__init__.py:

    from echo_noise import echo_noise

-And that's it. The user now accesses this function like any other function -from the filters package:

    >>> import sounds.filters
-    >>> sounds.filters.echo_noise(...)

Extending Wrapped Objects in Python

-Thanks to Python's flexibility, you can easily add new methods to a class, -even after it was already created:

    >>> class C(object): pass
-    >>>
-    >>># a regular function
-    >>> def C_str(self): return 'A C instance!'
-    >>>
-    >>># now we turn it in a member function
-    >>> C.__str__ = C_str
-    >>>
-    >>> c = C()
-    >>> print c
-    A C instance!
-    >>> C_str(c)
-    A C instance!

-Yes, Python rox.

-We can do the same with classes that were wrapped with Boost.Python. Suppose -we have a class point in C++:

    class point {...};
-
-    BOOST_PYTHON_MODULE(_geom)
-    {
-        class_<point>("point")...;
-    }

-If we are using the technique from the previous session, - -Creating Packages, we can code directly into geom/__init__.py:

    from _geom import *# a regular function
-    def point_str(self):
-        return str((self.x, self.y))# now we turn it into a member function
-    point.__str__ = point_str

All point instances created from C++ will also have this member function! -This technique has several advantages:

  • -Cut down compile times to zero for these additional functions -
  • -Reduce the memory footprint to virtually zero -
  • -Minimize the need to recompile -
  • -Rapid prototyping (you can move the code to C++ if required without changing the interface) -

-You can even add a little syntactic sugar with the use of metaclasses. Let's -create a special metaclass that "injects" methods in other classes.


-# The one Boost.Python uses for all wrapped classes.# You can use here any class exported by Boost instead of "point"
-    BoostPythonMetaclass = point.__class__
-
-    class injector(object):
-        class __metaclass__(BoostPythonMetaclass):
-            def __init__(self, name, bases, dict):
-                for b in bases:
-                    if type(b) not in (self, type):
-                        for k,v in dict.items():
-                            setattr(b,k,v)
-                return type.__init__(self, name, bases, dict)# inject some methods in the point foo
-    class more_point(injector, point):
-        def __repr__(self):
-            return 'Point(x=%s, y=%s)' % (self.x, self.y)
-        def foo(self):
-            print 'foo!'

-Now let's see how it got:

    >>> print point()
-    Point(x=10, y=10)
-    >>> point().foo()
-    foo!

-Another useful idea is to replace constructors with factory functions:

    _point = point
-
-    def point(x=0, y=0):
-        return _point(x, y)

-In this simple case there is not much gained, but for constructurs with -many overloads and/or arguments this is often a great simplification, again -with virtually zero memory footprint and zero compile-time overhead for -the keyword support.

Reducing Compiling Time

-If you have ever exported a lot of classes, you know that it takes quite a good -time to compile the Boost.Python wrappers. Plus the memory consumption can -easily become too high. If this is causing you problems, you can split the -class_ definitions in multiple files:

    /* file point.cpp */
-    #include <point.h>
-    #include <boost/python.hpp>
-
-    void export_point()
-    {
-        class_<point>("point")...;
-    }
-
-    /* file triangle.cpp */
-    #include <triangle.h>
-    #include <boost/python.hpp>
-
-    void export_triangle()
-    {
-        class_<triangle>("triangle")...;
-    }

-Now you create a file main.cpp, which contains the BOOST_PYTHON_MODULE -macro, and call the various export functions inside it.

    void export_point();
-    void export_triangle();
-
-    BOOST_PYTHON_MODULE(_geom)
-    {
-        export_point();
-        export_triangle();
-    }

-Compiling and linking together all this files produces the same result as the -usual approach:

    #include <boost/python.hpp>
-    #include <point.h>
-    #include <triangle.h>
-
-    BOOST_PYTHON_MODULE(_geom)
-    {
-        class_<point>("point")...;
-        class_<triangle>("triangle")...;
-    }

-but the memory is kept under control.

-This method is recommended too if you are developing the C++ library and -exporting it to Python at the same time: changes in a class will only demand -the compilation of a single cpp, instead of the entire wrapper code.

If you're exporting your classes with -Pyste, -take a look at the --multiple option, that generates the wrappers in -various files as demonstrated here.
This method is useful too if you are getting the error message -"fatal error C1204:Compiler limit:internal structure overflow" when compiling -a large source file, as explained in the -FAQ.
Copyright © 2002-2004 Joel de Guzman, David Abrahams

PrevUpHome
diff --git a/doc/tutorial/doc/html/boostbook.css b/doc/tutorial/doc/html/boostbook.css index d32581b7..e3db08f3 100644 --- a/doc/tutorial/doc/html/boostbook.css +++ b/doc/tutorial/doc/html/boostbook.css @@ -14,11 +14,8 @@ documentation. */ body { padding: 2em 1em 2em 1em; - background-color: #FFFFFF; - margin: 1em 2em 1em 2em; + margin: 1em 1em 1em 1em; font-family: sans-serif; - color: black; - background: white; } /* Paragraphs */ @@ -27,32 +24,42 @@ p text-align: justify; } +pre.synopsis +{ + margin: 1pc 4% 0pc 4%; + padding: 0.5pc 0.5pc 0.5pc 0.5pc; +} + /* Headings */ -h1, h2, h3, h4, h5, h6 { text-align: left } -h1, h2, h3 { color: #005A9C; } +h1, h2, h3, h4, h5, h6 { text-align: left; margin-top: 2pc; } h1 { font: 170% sans-serif } h2 { font: bold 140% sans-serif } h3 { font: 120% sans-serif } h4 { font: bold 100% sans-serif } h5 { font: italic 100% sans-serif } -h6 { font: small-caps 100% sans-serif } +h6 { font: italic 100% sans-serif } /* Unordered lists */ ul { - list-style-image: url(images/bullet.gif); + text-align: justify; } /* Links */ -a +a { - font-weight: bold; - color: #003366; text-decoration: none; /* no underline */ } +a:hover +{ + text-decoration: underline; +} + /* Top page title */ -.title/*, .refnamediv h2, .standalone-title */ +title, h1.title, h2.title, h3.title, + h4.title, h5.title, h6.title, + .refentrytitle { font-weight: bold; font-size: 2pc; @@ -71,46 +78,25 @@ a padding-left: 0.5em; } -/* Program listing box */ -.programlisting /*, .table-programlisting*/ +.spirit-nav img { - background-color: #f5f5f5; - border: gray 1pt solid; - display: block; - padding-top: 4px; - padding-right: 4px; - padding-bottom: 4px; - padding-left: 4px; + border-width: 0px; } -/* Syntax Highlighting */ -.keyword { color: #000099} -.identifier {} -.special { color: #800040} -.preprocessor { color: #FF0000} -.char { color: #6666FF; font-style: italic} -.comment { font-style: italic; color: #990000} -.string { font-style: italic; color: #666666} -.number { color: #6666FF; font-style: italic} -.quotes { color: #666666; font-style: italic; font-weight: bold} -.copyright { color: #666666; font-size: small} -.white_bkd { background-color: #FFFFFF} -.dk_grey_bkd { background-color: #999999} - -/* Literals */ -tt.literal +/* Program listing box */ +.programlisting, .screen { - color: #000050 -} + display: block; + margin-left: 4%; + margin-right: 4%; + padding: 0.5pc 0.5pc 0.5pc 0.5pc; +} /* Table of contents */ .toc { - margin-left: 15%; - margin-right: 15%; - margin-top: 1pc; - margin-bottom: 0pc; - padding: 0.5pc; + margin: 1pc 4% 0pc 4%; + padding: 0.5pc 0.5pc 0.5pc 0.5pc; } .boost-toc @@ -120,46 +106,190 @@ tt.literal } /* Tables */ -.table-title +.table-title, div.table p.title { - color: #005A9C; - margin-left: 8%; + margin-left: 4%; padding-right: 0.5em; - padding-left: 0.5em; + padding-left: 0.5em; + font-size: 120%; } -.informaltable table +.informaltable table, .table table { - margin-left: 8%; - margin-right: 8%; + width: 92%; + margin-left: 4%; + margin-right: 4%; } -div.informaltable table +div.informaltable table, div.table table { - background-color: white; - padding-top: 4px; - padding-right: 4px; - padding-bottom: 4px; - padding-left: 4px; + padding: 4px 4px 4px 4px; } -div.informaltable table tr td +div.informaltable table tr td, div.table table tr td { - background-color: #D9EFFF; - padding-top: 0.5em; - padding-right: 0.5em; - padding-bottom: 0.5em; - padding-left: 0.5em; + padding: 0.5em 0.5em 0.5em 0.5em; + text-align: justify; +} + +div.informaltable table tr th, div.table table tr th +{ + padding: 0.5em 0.5em 0.5em 0.5em; + border: 1pt solid white; } /* inlined images */ .inlinemediaobject { - padding-top: 0.5em; - padding-right: 0.5em; - padding-bottom: 0.5em; - padding-left: 0.5em; + padding: 0.5em 0.5em 0.5em 0.5em; +} + +/* tone down the title of Parameter lists */ +div.variablelist p.title +{ + font-weight: bold; + font-size: 100%; + text-align: left; +} + +/* tabularize parameter lists */ +div.variablelist dl dt +{ + float: left; + clear: left; + display: block; + font-style: italic; +} + +div.variablelist dl dd +{ + display: block; + clear: right; + padding-left: 8pc; +} + +/* title of books and articles in bibliographies */ +span.title +{ + font-style: italic; } +@media screen +{ + a + { + color: #005a9c; + } + a:visited + { + color: #9c5a9c; + } + + /* Syntax Highlighting */ + .keyword { color: #0000AA; font-weight: bold; } + .identifier {} + .special { color: #707070; } + .preprocessor { color: #402080; font-weight: bold; } + .char { color: teal; } + .comment { color: #800000; } + .string { color: teal; } + .number { color: teal; } + .copyright { color: #666666; font-size: small; } + .white_bkd { background-color: #FFFFFF; } + .dk_grey_bkd { background-color: #999999; } + + pre.synopsis + { + background-color: #f3f3f3; + } + + .programlisting, .screen + { + background-color: #f3f3f3; + } + + /* Table of contents */ + .toc + { + background-color: #f3f3f3; + } + + div.informaltable table tr td, div.table table tr td + { + background-color: #F3F3F3; + border: 1pt solid white; + } + + div.informaltable table tr th, div.table table tr th + { + background-color: #e4e4e4; + } + + span.highlight + { + color: #00A000; + } +} + +@media print +{ + a + { + color: black; + } + + a:visited + { + color: black; + } + + .spirit-nav + { + display: none; + } + + /* Syntax Highlighting */ + .keyword + { + font-weight: bold; + } + + pre.synopsis + { + border: 1px solid gray; + } + + .programlisting, .screen + { + border: 1px solid gray; + } + + /* Table of contents */ + .toc + { + border: 1px solid gray; + } + + .informaltable table, .table table + { + border: 1px solid gray; + border-collapse: collapse; + } + + div.informaltable table tr td, div.table table tr td + { + border: 1px solid gray; + } + + div.informaltable table tr th, div.table table tr th + { + border: 1px solid gray; + } + + span.highlight + { + font-weight: bold; + } +} diff --git a/doc/tutorial/doc/html/images/alert.png b/doc/tutorial/doc/html/images/alert.png new file mode 100755 index 00000000..b4645bc7 Binary files /dev/null and b/doc/tutorial/doc/html/images/alert.png differ diff --git a/doc/tutorial/doc/html/images/home.png b/doc/tutorial/doc/html/images/home.png new file mode 100755 index 00000000..5584aacb Binary files /dev/null and b/doc/tutorial/doc/html/images/home.png differ diff --git a/doc/tutorial/doc/html/images/jam.png b/doc/tutorial/doc/html/images/jam.png new file mode 100644 index 00000000..224ed791 Binary files /dev/null and b/doc/tutorial/doc/html/images/jam.png differ diff --git a/doc/tutorial/doc/html/images/next.png b/doc/tutorial/doc/html/images/next.png new file mode 100755 index 00000000..59800b4e Binary files /dev/null and b/doc/tutorial/doc/html/images/next.png differ diff --git a/doc/tutorial/doc/html/images/note.png b/doc/tutorial/doc/html/images/note.png new file mode 100755 index 00000000..3ed047ca Binary files /dev/null and b/doc/tutorial/doc/html/images/note.png differ diff --git a/doc/tutorial/doc/html/images/prev.png b/doc/tutorial/doc/html/images/prev.png new file mode 100755 index 00000000..d88a40f9 Binary files /dev/null and b/doc/tutorial/doc/html/images/prev.png differ diff --git a/doc/tutorial/doc/html/images/python.png b/doc/tutorial/doc/html/images/python.png new file mode 100644 index 00000000..cc2ff1d5 Binary files /dev/null and b/doc/tutorial/doc/html/images/python.png differ diff --git a/doc/tutorial/doc/html/images/smiley.png b/doc/tutorial/doc/html/images/smiley.png new file mode 100644 index 00000000..30a77f71 Binary files /dev/null and b/doc/tutorial/doc/html/images/smiley.png differ diff --git a/doc/tutorial/doc/html/images/tip.png b/doc/tutorial/doc/html/images/tip.png new file mode 100755 index 00000000..9f596b0b Binary files /dev/null and b/doc/tutorial/doc/html/images/tip.png differ diff --git a/doc/tutorial/doc/html/images/up.png b/doc/tutorial/doc/html/images/up.png new file mode 100755 index 00000000..17d9c3ec Binary files /dev/null and b/doc/tutorial/doc/html/images/up.png differ diff --git a/doc/tutorial/doc/html/index.html b/doc/tutorial/doc/html/index.html index 2729f83e..b43a13f3 100644 --- a/doc/tutorial/doc/html/index.html +++ b/doc/tutorial/doc/html/index.html @@ -1,7 +1,95 @@ -Chapter 1. Boost.Boost Python 1.0
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore

Next

Chapter 1. Boost.Boost Python 1.0

Joel de Guzman

David Abrahams

Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -

QuickStart

+ + + +Chapter 1. python 1.0 + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+


+
Next
+
+
+
+

+Chapter 1. python 1.0

+

+Joel de Guzman +

+

+David Abrahams +

+
+

+ Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + + http://www.boost.org/LICENSE_1_0.txt + ) + +

+
+
+
+ +
+
+

+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 @@ -11,20 +99,39 @@ 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).

Hello World

+definition language (IDL).

+

+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 <boost/python.hpp>
-    using namespace boost::python;
+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);
-    }

+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...

Last revised: September 16, 2004 at 03:53:11 GMT


Next
+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...

+
+
+ + + +

Last revised: October 12, 2004 at 03:11:11 GMT

+
+
Next
+ + diff --git a/doc/tutorial/doc/html/python/embedding.html b/doc/tutorial/doc/html/python/embedding.html new file mode 100644 index 00000000..9069dc13 --- /dev/null +++ b/doc/tutorial/doc/html/python/embedding.html @@ -0,0 +1,347 @@ + + + +Embedding + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+Embedding

+
+
+ +

+By now you should know how to use Boost.Python to call your C++ code from +Python. However, sometimes you may need to do the reverse: call Python code +from the C++-side. This requires you to embed the Python interpreter +into your C++ program.

+

+Currently, Boost.Python does not directly support everything you'll need +when embedding. Therefore you'll need to use the +Python/C API to fill in +the gaps. However, Boost.Python already makes embedding a lot easier and, +in a future version, it may become unnecessary to touch the Python/C API at +all. So stay tuned...

+

+Building embedded programs

+

+To be able to use embedding in your programs, they have to be linked to +both Boost.Python's and Python's static link library.

+

+Boost.Python's static link library comes in two variants. Both are located +in Boost's /libs/python/build/bin-stage subdirectory. On Windows, the +variants are called boost_python.lib (for release builds) and +boost_python_debug.lib (for debugging). If you can't find the libraries, +you probably haven't built Boost.Python yet. See and Testing on how to do this.

+

+Python's static link library can be found in the /libs subdirectory of +your Python directory. On Windows it is called pythonXY.lib where X.Y is +your major Python version number.

+

+Additionally, Python's /include subdirectory has to be added to your +include path.

+

+In a Jamfile, all the above boils down to:

+
    projectroot c:\projects\embedded_program ; # location of the program
+
+    # bring in the rules for python
+    SEARCH on python.jam = $(BOOST_BUILD_PATH) ;
+    include python.jam ;
+
+    exe embedded_program # name of the executable
+      : #sources
+         embedded_program.cpp
+      : # requirements
+         <find-library>boost_python <library-path>c:\boost\libs\python
+      $(PYTHON_PROPERTIES)
+        <library-path>$(PYTHON_LIB_PATH)
+        <find-library>$(PYTHON_EMBEDDED_LIBRARY) ;
+
+

+Getting started

+

+Being able to build is nice, but there is nothing to build yet. Embedding +the Python interpreter into one of your C++ programs requires these 4 +steps:

+
    +
  1. +#include <boost/python.hpp>

    +

    +
  2. +
  3. +Call Py_Initialize() to start the interpreter and create the _main_ module.

    +

    +
  4. +
  5. +Call other Python C API routines to use the interpreter.

    +

    +
  6. +
  7. +Call Py_Finalize() to stop the interpreter and release its resources. +
  8. +
+

+(Of course, there can be other C++ code between all of these steps.)

+

Now that we can embed the interpreter in our programs, lets see how to put it to use...

+
+
+

+Using the interpreter

+
+
+

+As you probably already know, objects in Python are reference-counted. +Naturally, the PyObjects of the Python/C API are also reference-counted. +There is a difference however. While the reference-counting is fully +automatic in Python, the Python/C API requires you to do it +by hand. This is +messy and especially hard to get right in the presence of C++ exceptions. +Fortunately Boost.Python provides the handle and +object class templates to automate the process.

+

+Reference-counting handles and objects

+

+There are two ways in which a function in the Python/C API can return a +PyObject*: as a borrowed reference or as a new reference. Which of +these a function uses, is listed in that function's documentation. The two +require slightely different approaches to reference-counting but both can +be 'handled' by Boost.Python.

+

+For a function returning a borrowed reference we'll have to tell the +handle that the PyObject* is borrowed with the aptly named +borrowed function. Two functions +returning borrowed references are PyImport_AddModule and PyModule_GetDict. +The former returns a reference to an already imported module, the latter +retrieves a module's namespace dictionary. Let's use them to retrieve the +namespace of the _main_ module:

+
object main_module((
+    handle<>(borrowed(PyImport_AddModule("__main__")))));
+
+object main_namespace = main_module.attr("__dict__");
+

+For a function returning a new reference we can just create a handle +out of the raw PyObject* without wrapping it in a call to borrowed. One +such function that returns a new reference is PyRun_String which we'll +discuss in the next section.

+
+ + +
+Handle is a class template, so why haven't we been using any template parameters?

+

+handle has a single template parameter specifying the type of the managed object. This type is PyObject 99% of the time, so the parameter was defaulted to PyObject for convenience. Therefore we can use the shorthand handle<> instead of the longer, but equivalent, handle<PyObject>. +
+

+Running Python code

+

+To run Python code from C++ there is a family of functions in the API +starting with the PyRun prefix. You can find the full list of these +functions here. They +all work similarly so we will look at only one of them, namely:

+
PyObject* PyRun_String(char *str, int start, PyObject *globals, PyObject *locals)
+

PyRun_String takes the code to execute as a null-terminated (C-style) +string in its str parameter. The function returns a new reference to a +Python object. Which object is returned depends on the start paramater.

+

+The start parameter is the start symbol from the Python grammar to use +for interpreting the code. The possible values are:

+
+

+Start symbols +

+ ++++ + + + + + + + + + + + + + + +
Py_eval_inputfor interpreting isolated expressions
Py_file_inputfor interpreting sequences of statements
Py_single_inputfor interpreting a single statement
+
+

+When using Py_eval_input, the input string must contain a single expression +and its result is returned. When using Py_file_input, the string can +contain an abitrary number of statements and None is returned. +Py_single_input works in the same way as Py_file_input but only accepts a +single statement.

+

+Lastly, the globals and locals parameters are Python dictionaries +containing the globals and locals of the context in which to run the code. +For most intents and purposes you can use the namespace dictionary of the +_main_ module for both parameters.

+

+We have already seen how to get the _main_ module's namespace so let's +run some Python code in it:

+
object main_module((
+    handle<>(borrowed(PyImport_AddModule("__main__")))));
+
+object main_namespace = main_module.attr("__dict__");
+
+handle<> ignored((PyRun_String(
+
+    "hello = file('hello.txt', 'w')\n"
+    "hello.write('Hello world!')\n"
+    "hello.close()"
+
+  , Py_file_input
+  , main_namespace.ptr()
+  , main_namespace.ptr())
+));
+

+Because the Python/C API doesn't know anything about objects, we used +the object's ptr member function to retrieve the PyObject*.

+

+This should create a file called 'hello.txt' in the current directory +containing a phrase that is well-known in programming circles.

+

Note that we wrap the return value of PyRun_String in a +(nameless) handle even though we are not interested in it. If we didn't +do this, the the returned object would be kept alive unnecessarily. Unless +you want to be a Dr. Frankenstein, always wrap PyObject*s in handles.

+

+Beyond handles

+

+It's nice that handle manages the reference counting details for us, but +other than that it doesn't do much. Often we'd like to have a more useful +class to manipulate Python objects. But we have already seen such a class +above, and in the previous section: the aptly +named object class and it's derivatives. We've already seen that they +can be constructed from a handle. The following examples should further +illustrate this fact:

+
object main_module((
+     handle<>(borrowed(PyImport_AddModule("__main__")))));
+
+object main_namespace = main_module.attr("__dict__");
+
+handle<> ignored((PyRun_String(
+
+    "result = 5 ** 2"
+
+    , Py_file_input
+    , main_namespace.ptr()
+    , main_namespace.ptr())
+));
+
+int five_squared = extract<int>(main_namespace["result"]);
+

+Here we create a dictionary object for the _main_ module's namespace. +Then we assign 5 squared to the result variable and read this variable from +the dictionary. Another way to achieve the same result is to let +PyRun_String return the result directly with Py_eval_input:

+
object result((handle<>(
+    PyRun_String("5 ** 2"
+        , Py_eval_input
+        , main_namespace.ptr()
+        , main_namespace.ptr()))
+));
+
+int five_squared = extract<int>(result);
+

Note that object's member function to return the wrapped +PyObject* is called ptr instead of get. This makes sense if you +take into account the different functions that object and handle +perform.

+

+Exception handling

+

+If an exception occurs in the execution of some Python code, the PyRun_String +function returns a null pointer. Constructing a handle out of this null +pointer throws error_already_set, +so basically, the Python exception is automatically translated into a +C++ exception when using handle:

+
try
+{
+    object result((handle<>(PyRun_String(
+        "5/0"
+      , Py_eval_input
+      , main_namespace.ptr()
+      , main_namespace.ptr()))
+    ));
+
+    // execution will never get here:
+    int five_divided_by_zero = extract<int>(result);
+}
+catch(error_already_set)
+{
+    // handle the exception in some way
+}
+

+The error_already_set exception class doesn't carry any information in itself. +To find out more about the Python exception that occurred, you need to use the +exception handling functions +of the Python/C API in your catch-statement. This can be as simple as calling +PyErr_Print() to +print the exception's traceback to the console, or comparing the type of the +exception with those of the +standard exceptions:

+
catch(error_already_set)
+{
+    if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
+    {
+        // handle ZeroDivisionError specially
+    }
+    else
+    {
+        // print all other errors to stderr
+        PyErr_Print();
+    }
+}
+

+(To retrieve even more information from the exception you can use some of the other +exception handling functions listed here.)

+

+If you'd rather not have handle throw a C++ exception when it is constructed, you +can use the allow_null function in the same +way you'd use borrowed:

+
handle<> result((allow_null(PyRun_String(
+    "5/0"
+   , Py_eval_input
+   , main_namespace.ptr()
+   , main_namespace.ptr()))));
+
+if (!result)
+    // Python exception occurred
+else
+    // everything went okay, it's safe to use the result
+
+
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/exception.html b/doc/tutorial/doc/html/python/exception.html new file mode 100644 index 00000000..b5d39fad --- /dev/null +++ b/doc/tutorial/doc/html/python/exception.html @@ -0,0 +1,57 @@ + + + + Exception Translation + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+ 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 const& x) {
+    PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave...");
+}
+BOOST_PYTHON_MODULE(kubrick) {
+     register_exception_translator<
+          PodBayDoorException>(translator);
+     ...
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/exposing.html b/doc/tutorial/doc/html/python/exposing.html new file mode 100644 index 00000000..6bf7a744 --- /dev/null +++ b/doc/tutorial/doc/html/python/exposing.html @@ -0,0 +1,580 @@ + + + + Exposing Classes + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+ 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'
+
+
+

+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.

+
+
+
+

+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, assuming we have placed our Var class inside the namespace +hello as we did before:

+
>>> x = hello.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
+
+
+
+
+

+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", &Num::get)
+    .add_property("value", &Num::get, &Num::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", &Num::get)
+
+
+
+

+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:

+
    +
  1. +Derived automatically inherits all of Base's Python methods (wrapped C++ member functions) +
  2. +
  3. +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. +
  4. +
+

+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>());
+
+
+
+

+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;
+};
+
+
+struct BaseWrap : Base
+{
+    BaseWrap(PyObject* self_)
+        : self(self_) {}
+    BaseWrap(PyObject* self_, Base const& copy)
+        : Base(copy), self(self_) {}
+    int f() { return call_method<int>(self, "f"); }
+    int default_f() { return Base::f(); } // <<=== ***ADDED***
+    PyObject* self;
+};
+
+ + +
+member function and methods

+

+ 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.

+
+ + +
+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 fromPython. 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 fromC++.]

+

+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()
+RuntimeError: This class cannot be instantiated from Python
+

+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.

+
+
+
+

+Deriving a Python Class

+
+
+

+Continuing, we can derive from our base class Base in Python and override +the virtual function in Python. Before we can do that, we have to set up +our class_ wrapper as:

+
class_<Base, BaseWrap, boost::noncopyable>("Base")
+    ;
+

+Otherwise, we have to suppress the Base class' no_init by adding an +_init_() method to all our derived classes. no_init actually adds +an _init_ method that raises a Python RuntimeError exception.

+
>>> 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:

+
    +
  1. +call_f(derived) is called in Python +
  2. +
  3. +This corresponds to def("call_f", call_f);. Boost.Python dispatches this call. +
  4. +
  5. +int call_f(Base& b) { return b.f(); } accepts the call. +
  6. +
  7. +The overridden virtual function f of BaseWrap is called. +
  8. +
  9. +call_method<int>(self, "f"); dispatches the call back to Python. +
  10. +
  11. +def f(self): return 42 is finally called. +
  12. +
+
+
+
+

+Virtual Functions with Default Implementations

+
+
+

+Recall that in the previous section, we +wrapped a class with a pure virtual function that we then implemented in +C++ or Python classes derived from it. Our base class:

+
struct Base
+{
+    virtual int f() = 0;
+};
+

+had a pure virtual function f. If, however, its member function f was +not declared as pure virtual:

+
struct Base
+{
+    virtual int f() { return 0; }
+};
+

+and instead had a default implementation that returns 0, as shown above, +we need to add a forwarding function that calls the Base default virtual +function f implementation:

+
struct BaseWrap : Base
+{
+    BaseWrap(PyObject* self_)
+        : self(self_) {}
+    int f() { return call_method<int>(self, "f"); }
+    int default_f() { return Base::f(); } // <<=== ***ADDED***
+    PyObject* self;
+};
+

+Then, Boost.Python needs to keep track of 1) the dispatch function f and +2) the forwarding function to its default implementation default_f. +There's a special def function for this purpose. Here's how it is +applied to our example above:

+
class_<Base, BaseWrap, BaseWrap, boost::noncopyable>("Base")
+    .def("f", &Base::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
+
+
+
+

+Class Operators/Special Functions

+
+
+

+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>("FilePos")
+    .def(self + int())          // __add__
+    .def(int() + self)          // __radd__
+    .def(self - self)           // __sub__
+    .def(self - int())          // __sub__
+    .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".

+

+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_<Rational>()
+    .def(float_(self))                  // __float__
+    .def(pow(self, other<Rational>))    // __pow__
+    .def(abs(self))                     // __abs__
+    .def(str(self))                     // __str__
+    ;
+

+Need we say more?

+
+ + +
+ 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)).
+
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/functions.html b/doc/tutorial/doc/html/python/functions.html new file mode 100644 index 00000000..fb6ac3c8 --- /dev/null +++ b/doc/tutorial/doc/html/python/functions.html @@ -0,0 +1,494 @@ + + + +Functions + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+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!
+
+
+
+

+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:

+
    +
  1. +f is called passing in a reference to y and a pointer to z +
  2. +
  3. +A reference to y.x is returned +
  4. +
  5. +y is deleted. x is a dangling reference +
  6. +
  7. +x.some_method() is called +
  8. +
  9. BOOM!
  10. +
+

+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:

+
    +
  1. +f is called passing in a reference to y and a pointer to z +
  2. +
  3. +A pointer to z is held by y +
  4. +
  5. +A reference to y.x is returned +
  6. +
  7. +z is deleted. y.z is a dangling pointer +
  8. +
  9. +y.z_value() is called +
  10. +
  11. +z->value() is called +
  12. +
  13. BOOM!
  14. +
+

+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<args...,
+    policy2<args...,
+        policy3<args...> > >
+

+Here is the list of predefined call policies. A complete reference detailing +these can be found here.

+
    +
  • +with_custodian_and_ward

    + Ties lifetimes of the arguments +
  • +
  • +with_custodian_and_ward_postcall

    + Ties lifetimes of the arguments and results +
  • +
  • +return_internal_reference

    + Ties lifetime of one argument to that of result +
  • +
  • +return_value_policy<T> with T one of:

    +
  • +
  • +reference_existing_object

    +naive (dangerous) approach +
  • +
  • +copy_const_reference

    +Boost.Python v1 approach +
  • +
  • +copy_non_const_reference

    +
  • +
  • +manage_new_object

    + Adopt a pointer and hold the instance +
  • +
+
+ + +
+Remember the Zen, Luke:

+

+ +"Explicit is better than implicit"

+ +"In the face of ambiguity, refuse the temptation to guess"

+
+
+
+
+

+Overloading

+
+
+

+The following illustrates a scheme for manually wrapping an overloaded +member functions. Of course, the same technique can be applied to wrapping +overloaded non-member functions.

+

+We have here our C++ class:

+
struct X
+{
+    bool f(int a)
+    {
+        return true;
+    }
+
+    bool f(int a, double b)
+    {
+        return true;
+    }
+
+    bool f(int a, double b, char c)
+    {
+        return true;
+    }
+
+    int f(int a, int b, int c)
+    {
+        return a + b + c;
+    };
+};
+

+Class X has 4 overloaded functions. We shall start by introducing some +member function pointer variables:

+
bool    (X::*fx1)(int)              = &X::f;
+bool    (X::*fx2)(int, double)      = &X::f;
+bool    (X::*fx3)(int, double, char)= &X::f;
+int     (X::*fx4)(int, int, int)    = &X::f;
+

+With these in hand, we can proceed to define and wrap this for Python:

+
.def("f", fx1)
+.def("f", fx2)
+.def("f", fx3)
+.def("f", fx4)
+
+
+
+

+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, we had to resort to manual +wrapping as outlined in the previous section, or +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_FUNCTION_OVERLOADS

+

+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());
+

+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS

+

+Objects here, objects there, objects here there everywhere. More frequently +than anything else, we need to expose member functions of our classes to +Python. Then again, we have the same inconveniences as before when default +arguments or overloads with a common sequence of initial arguments come +into play. Another macro is provided to make this a breeze.

+

+Like BOOST_PYTHON_FUNCTION_OVERLOADS, +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS may be used to automatically create +the thin wrappers for wrapping member functions. Let's have an example:

+
struct george
+{
+    void
+    wack_em(int a, int b = 0, char c = 'x')
+    {
+        /*...*/
+    }
+};
+

+The macro invocation:

+
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(george_overloads, wack_em, 1, 3)
+

+will generate a set of thin wrappers for george's wack_em member function +accepting a minimum of 1 and a maximum of 3 arguments (i.e. the third and +fourth macro argument). The thin wrappers are all enclosed in a class named +george_overloads that can then be used as an argument to def(...):

+
.def("wack_em", &george::wack_em, george_overloads());
+

+See the overloads reference +for details.

+

+init and optional

+

+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).

+
+
+
+

+Auto-Overloading

+
+
+

+It was mentioned in passing in the previous section that +BOOST_PYTHON_FUNCTION_OVERLOADS and BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS +can also be used for overloaded functions and member functions with a +common sequence of initial arguments. Here is an example:

+
void foo()
+{
+   /*...*/
+}
+
+void foo(bool a)
+{
+   /*...*/
+}
+
+void foo(bool a, int b)
+{
+   /*...*/
+}
+
+void foo(bool a, int b, char c)
+{
+   /*...*/
+}
+

+Like in the previous section, we can generate thin wrappers for these +overloaded functions in one-shot:

+
BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)
+

+Then...

+
.def("foo", foo, foo_overloads());
+

+Notice though that we have a situation now where we have a minimum of zero +(0) arguments and a maximum of 3 arguments.

+

+Manual Wrapping

+

+It is important to emphasize however that the overloaded functions must +have a common sequence of initial arguments. Otherwise, our scheme above +will not work. If this is not the case, we have to wrap our functions +manually.

+

+Actually, we can mix and match manual wrapping of overloaded functions and +automatic wrapping through BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS and +its sister, BOOST_PYTHON_FUNCTION_OVERLOADS. Following up on our example +presented in the section on overloading, since the +first 4 overload functins have a common sequence of initial arguments, we +can use BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS to automatically wrap the +first three of the defs and manually wrap just the last. Here's +how we'll do this:

+
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xf_overloads, f, 1, 4)
+

+Create a member function pointers as above for both X::f overloads:

+
bool    (X::*fx1)(int, double, char)    = &X::f;
+int     (X::*fx2)(int, int, int)        = &X::f;
+

+Then...

+
.def("f", fx1, xf_overloads());
+.def("f", fx2)
+
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/hello.html b/doc/tutorial/doc/html/python/hello.html new file mode 100644 index 00000000..01fa0016 --- /dev/null +++ b/doc/tutorial/doc/html/python/hello.html @@ -0,0 +1,233 @@ + + + + Building Hello World + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+ Building Hello World

+
+
+

+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.

+
+ + +
+Building without bjam

+

+ + 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.

+

+ + Take note however that the preferred build tool for Boost.Python is bjam. + There are so many ways to set up the build incorrectly. Experience shows + that 90% of the "I can't build Boost.Python" problems come from people + who had to use a different tool. +
+

+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:

+
    +
  • +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. The complete list of Bjam executables can be found +here.

+

+Let's Jam!

+

+

+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
+        ;
+
+

+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.

+
+ + +
+ 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:

+
    +
  • +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!

+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/iterators.html b/doc/tutorial/doc/html/python/iterators.html new file mode 100644 index 00000000..56788475 --- /dev/null +++ b/doc/tutorial/doc/html/python/iterators.html @@ -0,0 +1,131 @@ + + + +Iterators + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+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<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:

+
    +
  • +range(start, finish) +
  • +
  • +range<Policies,Target>(start, finish) +
  • +
+

+Here, start/finish may be one of:

+
    +
  • +member data pointers +
  • +
  • +member function pointers +
  • +
  • +adaptable function object (use Target parameter) +
  • +
+

iterator

+
  • +iterator<T, Policies>() +
+

+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-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/object.html b/doc/tutorial/doc/html/python/object.html new file mode 100644 index 00000000..08b8a70d --- /dev/null +++ b/doc/tutorial/doc/html/python/object.html @@ -0,0 +1,273 @@ + + + + Object Interface + + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHomeNext +
+
+
+

+ 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++ objects are as close as possible to Python. This +should minimize the learning curve significantly.

+

+
+
+

+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, 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.

+
+
+
+

+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 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
+

+class_<T> as objects

+

+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);
+
+
+
+

+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 objecto. The second line attempts to extract the +Vec2 object from held by the Boost.Python objecto.

+

+Take note that we said "attempt to" above. What if the Boost.Python +objecto 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 the mutable copying problem:

+
dict d = extract<dict>(x.attr("__dict__"));
+d['whatever'] = 3;          # modifies x.__dict__ !
+
+
+
+

+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>("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.

+
+ + +
+what is a scope?

+

+ 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 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>("X")
+                .def( ... )
+                .def( ... )
+            ;
+
+// Expose X::nested as X.nested
+enum_<X::nested>("nested")
+    .value("red", red)
+    .value("blue", blue)
+    ;
+
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHomeNext +
+ + diff --git a/doc/tutorial/doc/html/python/techniques.html b/doc/tutorial/doc/html/python/techniques.html new file mode 100644 index 00000000..bebc6727 --- /dev/null +++ b/doc/tutorial/doc/html/python/techniques.html @@ -0,0 +1,373 @@ + + + + General Techniques + + + + + + + + + + + + + + +
boost.png (6897 bytes)HomeLibrariesPeopleFAQMore
+
+
+PrevUpHome +
+
+
+

+ General Techniques

+
+
+ +

+Here are presented some useful techniques that you can use while wrapping code with Boost.Python.

+
+
+

+Creating Packages

+
+
+

+A Python package is a collection of modules that provide to the user a certain +functionality. If you're not familiar on how to create packages, a good +introduction to them is provided in the +Python Tutorial.

+

+But we are wrapping C++ code, using Boost.Python. How can we provide a nice +package interface to our users? To better explain some concepts, let's work +with an example.

+

+We have a C++ library that works with sounds: reading and writing various +formats, applying filters to the sound data, etc. It is named (conveniently) +sounds. Our library already has a neat C++ namespace hierarchy, like so:

+
sounds::core
+sounds::io
+sounds::filters
+

+We would like to present this same hierarchy to the Python user, allowing him +to write code like this:

+
import sounds.filters
+sounds.filters.echo(...) # echo is a C++ function
+

+The first step is to write the wrapping code. We have to export each module +separately with Boost.Python, like this:

+
/* file core.cpp */
+BOOST_PYTHON_MODULE(core)
+{
+    /* export everything in the sounds::core namespace */
+    ...
+}
+
+/* file io.cpp */
+BOOST_PYTHON_MODULE(io)
+{
+    /* export everything in the sounds::io namespace */
+    ...
+}
+
+/* file filters.cpp */
+BOOST_PYTHON_MODULE(filters)
+{
+    /* export everything in the sounds::filters namespace */
+    ...
+}
+

+Compiling these files will generate the following Python extensions: +core.pyd, io.pyd and filters.pyd.

+
+ + +
+ The extension .pyd is used for python extension modules, which +are just shared libraries. Using the default for your system, like .so for +Unix and .dll for Windows, works just as well.
+

+Now, we create this directory structure for our Python package:

+
    sounds/
+        _init_.py
+        core.pyd
+        filters.pyd
+        io.pyd
+
+

+The file _init_.py is what tells Python that the directory sounds/ is +actually a Python package. It can be a empty file, but can also perform some +magic, that will be shown later.

+

+Now our package is ready. All the user has to do is put sounds into his +PYTHONPATH +and fire up the interpreter:

+
>>> import sounds.io
+>>> import sounds.filters
+>>> sound = sounds.io.open('file.mp3')
+>>> new_sound = sounds.filters.echo(sound, 1.0)
+

+Nice heh?

+

+This is the simplest way to create hierarchies of packages, but it is not very +flexible. What if we want to add a pure Python function to the filters +package, for instance, one that applies 3 filters in a sound object at once? +Sure, you can do this in C++ and export it, but why not do so in Python? You +don't have to recompile the extension modules, plus it will be easier to write +it.

+

+If we want this flexibility, we will have to complicate our package hierarchy a +little. First, we will have to change the name of the extension modules:

+
/* file core.cpp */
+BOOST_PYTHON_MODULE(_core)
+{
+    ...
+    /* export everything in the sounds::core namespace */
+}
+

+Note that we added an underscore to the module name. The filename will have to +be changed to _core.pyd as well, and we do the same to the other extension modules. +Now, we change our package hierarchy like so:

+
    sounds/
+        _init_.py
+        core/
+            _init_.py
+            _core.pyd
+        filters/
+            _init_.py
+            _filters.pyd
+        io/
+            _init_.py
+            _io.pyd
+
+

+Note that we created a directory for each extension module, and added a +_init_.py to each one. But if we leave it that way, the user will have to +access the functions in the core module with this syntax:

+
>>> import sounds.core._core
+>>> sounds.core._core.foo(...)
+

+which is not what we want. But here enters the _init_.py magic: everything +that is brought to the _init_.py namespace can be accessed directly by the +user. So, all we have to do is bring the entire namespace from _core.pyd +to core/_init.py]. So add this line of code to [^sounds/core/init_.py:

+
from _core import *
+

+We do the same for the other packages. Now the user accesses the functions and +classes in the extension modules like before:

+
>>> import sounds.filters
+>>> sounds.filters.echo(...)
+

+with the additional benefit that we can easily add pure Python functions to +any module, in a way that the user can't tell the difference between a C++ +function and a Python function. Let's add a pure Python function, +echo_noise, to the filters package. This function applies both the +echo and noise filters in sequence in the given sound object. We +create a file named sounds/filters/echo_noise.py and code our function:

+
import _filters
+def echo_noise(sound):
+    s = _filters.echo(sound)
+    s = _filters.noise(sound)
+    return s
+

+Next, we add this line to soundsfilters_init_.py:

+
from echo_noise import echo_noise
+

+And that's it. The user now accesses this function like any other function +from the filters package:

+
>>> import sounds.filters
+>>> sounds.filters.echo_noise(...)
+
+
+
+

+Extending Wrapped Objects in Python

+
+
+

+Thanks to Python's flexibility, you can easily add new methods to a class, +even after it was already created:

+
>>> class C(object): pass
+>>>
+>>> # a regular function
+>>> def C_str(self): return 'A C instance!'
+>>>
+>>> # now we turn it in a member function
+>>> C.__str__ = C_str
+>>>
+>>> c = C()
+>>> print c
+A C instance!
+>>> C_str(c)
+A C instance!
+

+Yes, Python rox.

+

+We can do the same with classes that were wrapped with Boost.Python. Suppose +we have a class point in C++:

+
class point {...};
+
+BOOST_PYTHON_MODULE(_geom)
+{
+    class_<point>("point")...;
+}
+

+If we are using the technique from the previous session, +Creating Packages, we can code directly +into geom/_init_.py:

+
from _geom import *
+
+# a regular function
+def point_str(self):
+    return str((self.x, self.y))
+
+# now we turn it into a member function
+point.__str__ = point_str
+

All point instances created from C++ will also have this member function! +This technique has several advantages:

+
    +
  • +Cut down compile times to zero for these additional functions +
  • +
  • +Reduce the memory footprint to virtually zero +
  • +
  • +Minimize the need to recompile +
  • +
  • +Rapid prototyping (you can move the code to C++ if required without changing the interface) +
  • +
+

+You can even add a little syntactic sugar with the use of metaclasses. Let's +create a special metaclass that "injects" methods in other classes.

+

+# The one Boost.Python uses for all wrapped classes.
+# You can use here any class exported by Boost instead of "point"
+BoostPythonMetaclass = point.__class__
+
+class injector(object):
+    class __metaclass__(BoostPythonMetaclass):
+        def __init__(self, name, bases, dict):
+            for b in bases:
+                if type(b) not in (self, type):
+                    for k,v in dict.items():
+                        setattr(b,k,v)
+            return type.__init__(self, name, bases, dict)
+
+# inject some methods in the point foo
+class more_point(injector, point):
+    def __repr__(self):
+        return 'Point(x=%s, y=%s)' % (self.x, self.y)
+    def foo(self):
+        print 'foo!'
+

+Now let's see how it got:

+
>>> print point()
+Point(x=10, y=10)
+>>> point().foo()
+foo!
+

+Another useful idea is to replace constructors with factory functions:

+
_point = point
+
+def point(x=0, y=0):
+    return _point(x, y)
+

+In this simple case there is not much gained, but for constructurs with +many overloads and/or arguments this is often a great simplification, again +with virtually zero memory footprint and zero compile-time overhead for +the keyword support.

+
+
+
+

+Reducing Compiling Time

+
+
+

+If you have ever exported a lot of classes, you know that it takes quite a good +time to compile the Boost.Python wrappers. Plus the memory consumption can +easily become too high. If this is causing you problems, you can split the +class_ definitions in multiple files:

+
/* file point.cpp */
+#include <point.h>
+#include <boost/python.hpp>
+
+void export_point()
+{
+    class_<point>("point")...;
+}
+
+/* file triangle.cpp */
+#include <triangle.h>
+#include <boost/python.hpp>
+
+void export_triangle()
+{
+    class_<triangle>("triangle")...;
+}
+

+Now you create a file main.cpp, which contains the BOOST_PYTHON_MODULE +macro, and call the various export functions inside it.

+
void export_point();
+void export_triangle();
+
+BOOST_PYTHON_MODULE(_geom)
+{
+    export_point();
+    export_triangle();
+}
+

+Compiling and linking together all this files produces the same result as the +usual approach:

+
#include <boost/python.hpp>
+#include <point.h>
+#include <triangle.h>
+
+BOOST_PYTHON_MODULE(_geom)
+{
+    class_<point>("point")...;
+    class_<triangle>("triangle")...;
+}
+

+but the memory is kept under control.

+

+This method is recommended too if you are developing the C++ library and +exporting it to Python at the same time: changes in a class will only demand +the compilation of a single cpp, instead of the entire wrapper code.

+
+ + +
+ If you're exporting your classes with Pyste, +take a look at the --multiple option, that generates the wrappers in +various files as demonstrated here.
+
+ + +
+ This method is useful too if you are getting the error message +"fatal error C1204:Compiler limit:internal structure overflow" when compiling +a large source file, as explained in the FAQ.
+
+
+ + + +
Copyright © 2002-2004 Joel de Guzman, David Abrahams
+
+
+PrevUpHome +
+ + diff --git a/doc/tutorial/doc/tutorial.qbk b/doc/tutorial/doc/tutorial.qbk index 53cf12d1..e58ccdc8 100644 --- a/doc/tutorial/doc/tutorial.qbk +++ b/doc/tutorial/doc/tutorial.qbk @@ -1,22 +1,28 @@ -[library Boost Python +[library python [version 1.0] - [authors Joel de Guzman, David Abrahams] + [authors [de Guzman, Joel], [Abrahams, David]] [copyright 2002 2003 2004 Joel de Guzman, David Abrahams] [category inter-language support] [purpose Reflects C++ classes and functions into Python ] + [license + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + + http://www.boost.org/LICENSE_1_0.txt + ) + ] ] [/ QuickBook Document version 0.9 ] -[def __note__ [$images/note.gif]] -[def __alert__ [$images/alert.gif]] -[def __detail__ [$images/lens.gif]] -[def __tip__ [$images/bulb.gif]] -[def :-) [$images/smiley.gif]] +[def __note__ [$../images/note.png]] +[def __alert__ [$../images/alert.png]] +[def __tip__ [$../images/tip.png]] +[def :-) [$../images/smiley.png]] -[beginpage QuickStart] +[section QuickStart] The Boost Python Library is a framework for interfacing Python and C++. It allows you to quickly and seamlessly expose C++ classes @@ -58,8 +64,8 @@ resulting DLL is now visible to Python. Here's a sample Python session: [:['[*Next stop... Building your Hello World module from start to finish...]]] -[endpage] -[beginpage:hello Building Hello World] +[endsect] +[section:hello Building Hello World] [h2 From Start To Finish] @@ -68,21 +74,19 @@ 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]. - -Take note however that the preferred build tool for Boost.Python is bjam. -There are so many ways to set up the build incorrectly. Experience shows -that 90% of the "I can't build Boost.Python" problems come from people -who had to use a different tool. +[blurb __note__ [*Building without bjam]\n\n + 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].\n\n + Take note however that the preferred build tool for Boost.Python is bjam. + There are so many ways to set up the build incorrectly. Experience shows + that 90% of the "I can't build Boost.Python" problems come from people + who had to use a different tool. ] 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]. +building Boost.Python, check out: [@../../../../building.html building.html]. After this brief ['bjam] tutorial, we should have built two DLLs: * boost_python.dll @@ -107,13 +111,11 @@ 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]. +platforms. The complete list of Bjam executables can be found +[@http://sourceforge.net/project/showfiles.php?group_id=7586 here]. [h2 Let's Jam!] -[$images/jam.png] +[$../images/jam.png] Here is our minimalist Jamfile: @@ -173,8 +175,10 @@ Python modules. Example: 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. +appropriately. + +[blurb __tip__ 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. @@ -185,7 +189,7 @@ Finally: 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 +[@../../../../../../../tools/build/index.html Building Boost Libraries] for further details. It should be building now: @@ -240,8 +244,8 @@ You may now fire up Python and run our hello module: [:[*There you go... Have fun!]] -[endpage] -[beginpage:exposing Exposing Classes] +[endsect] +[section:exposing Exposing Classes] Now let's expose a C++ class to Python. @@ -278,7 +282,7 @@ may use our class [^World] in Python. Here's a sample Python session: >>> planet.greet() 'howdy' -[beginpage Constructors] +[section Constructors] Our previous example didn't have any explicit constructors. Since [^World] is declared as a plain struct, it has an implicit default @@ -336,8 +340,8 @@ all, we may use [^no_init] instead: This actually adds an [^__init__] method which always raises a Python RuntimeError exception. -[endpage] -[beginpage Class Data Members] +[endsect] +[section 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 @@ -375,8 +379,8 @@ as [*read-write]. AttributeError: can't set attribute ] -[endpage] -[beginpage Class Properties] +[endsect] +[section Class Properties] In C++, classes with public data members are usually frowned upon. Well designed classes that take advantage of encapsulation hide @@ -414,8 +418,8 @@ since the [^rovalue] setter member function is not passed in: .add_property("rovalue", &Num::get) -[endpage] -[beginpage Inheritance] +[endsect] +[section 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 @@ -464,14 +468,14 @@ 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. +Boost.Python [@functions.html#python.call_policies call policies] later. // Tell Python to take ownership of factory's result def("factory", factory, return_value_policy()); -[endpage] -[beginpage Class Virtual Functions] +[endsect] +[section Class Virtual Functions] In this section, we shall learn how to make functions behave polymorphically through virtual functions. Continuing our example, let us @@ -512,7 +516,7 @@ need to create a class wrapper: PyObject* self; }; -[blurb __detail__ [*member function and methods][br][br] Python, like +[blurb __note__ [*member function and methods]\n\n Python, like many object oriented languages uses the term [*methods]. Methods correspond roughly to C++'s [*member functions]] @@ -521,17 +525,17 @@ 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]] +[blurb __note__ [*Why do we need BaseWrap?]\n\n] ['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] +Base."\n\n 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] +must use a derived class.\n\n 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 @@ -560,8 +564,8 @@ 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. -[endpage] -[beginpage Deriving a Python Class] +[endsect] +[section Deriving a Python Class] Continuing, we can derive from our base class Base in Python and override the virtual function in Python. Before we can do that, we have to set up @@ -607,10 +611,10 @@ Here's what's happening: # [^call_method(self, "f");] dispatches the call back to Python. # [^def f(self): return 42] is finally called. -[endpage] -[beginpage Virtual Functions with Default Implementations] +[endsect] +[section Virtual Functions with Default Implementations] -Recall that in the [@class_virtual_functions.html previous section], we +Recall that in the [@exposing.html#class_virtual_functions previous section], we wrapped a class with a pure virtual function that we then implemented in C++ or Python classes derived from it. Our base class: @@ -681,8 +685,8 @@ Calling [^call_f], passing in a [^derived] object: >>> call_f(derived) 42 -[endpage] -[beginpage Class Operators/Special Functions] +[endsect] +[section Class Operators/Special Functions] [h2 Python Operators] @@ -745,14 +749,14 @@ that correspond to these Python ['special functions]. Example: Need we say more? -[blurb __detail__ What is the business of [^operator<<] [^.def(str(self))]? +[blurb __note__ 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)).] -[endpage] -[endpage] [/ Exposing Classes ] +[endsect] +[endsect] [/ Exposing Classes ] -[beginpage Functions] +[section 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 @@ -790,7 +794,7 @@ But before you do, you might want to fire up Python 2.2 or later and type Namespaces are one honking great idea -- let's do more of those! ] -[beginpage Call Policies] +[section 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 @@ -914,23 +918,23 @@ or more policies can be composed by chaining. Here's the general syntax: policy3 > > Here is the list of predefined call policies. A complete reference detailing -these can be found [@../../v2/reference.html#models_of_call_policies here]. +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]naive (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 +* [*with_custodian_and_ward]\n Ties lifetimes of the arguments +* [*with_custodian_and_ward_postcall]\n Ties lifetimes of the arguments and results +* [*return_internal_reference]\n Ties lifetime of one argument to that of result +* [*return_value_policy with T one of:]\n +* [*reference_existing_object]\nnaive (dangerous) approach +* [*copy_const_reference]\nBoost.Python v1 approach +* [*copy_non_const_reference]\n +* [*manage_new_object]\n 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]] +[blurb :-) [*Remember the Zen, Luke:]\n\n +"Explicit is better than implicit"\n +"In the face of ambiguity, refuse the temptation to guess"\n] -[endpage] -[beginpage Overloading] +[endsect] +[section Overloading] The following illustrates a scheme for manually wrapping an overloaded member functions. Of course, the same technique can be applied to wrapping @@ -976,8 +980,8 @@ With these in hand, we can proceed to define and wrap this for Python: .def("f", fx3) .def("f", fx4) -[endpage] -[beginpage Default Arguments] +[endsect] +[section Default Arguments] Boost.Python wraps (member) function pointers. Unfortunately, C++ function pointers carry no default argument info. Take a function [^f] with default @@ -996,7 +1000,7 @@ to retrieve the default arguments: def("f", f); // defaults lost! Because of this, when wrapping C++ code, we had to resort to manual -wrapping as outlined in the [@overloading.html previous section], or +wrapping as outlined in the [@functions.html#overloading previous section], or writing thin wrappers: // write "thin wrappers" @@ -1069,7 +1073,7 @@ fourth macro argument). The thin wrappers are all enclosed in a class named .def("wack_em", &george::wack_em, george_overloads()); -See the [@../../v2/overloads.html#BOOST_PYTHON_FUNCTION_OVERLOADS-spec overloads reference] +See the [@../../../../v2/overloads.html#BOOST_PYTHON_FUNCTION_OVERLOADS-spec overloads reference] for details. [h2 init and optional] @@ -1091,8 +1095,8 @@ You can easily add this constructor to Boost.Python in one shot: Notice the use of [^init<...>] and [^optional<...>] to signify the default (optional arguments). -[endpage] -[beginpage Auto-Overloading] +[endsect] +[section Auto-Overloading] It was mentioned in passing in the previous section that [^BOOST_PYTHON_FUNCTION_OVERLOADS] and [^BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS] @@ -1136,12 +1140,12 @@ Notice though that we have a situation now where we have a minimum of zero It is important to emphasize however that [*the overloaded functions must have a common sequence of initial arguments]. Otherwise, our scheme above will not work. If this is not the case, we have to wrap our functions -[@overloading.html manually]. +[@functions.html#overloading manually]. Actually, we can mix and match manual wrapping of overloaded functions and automatic wrapping through [^BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS] and its sister, [^BOOST_PYTHON_FUNCTION_OVERLOADS]. Following up on our example -presented in the section [@overloading.html on overloading], since the +presented in the section [@functions.html#overloading on overloading], since the first 4 overload functins have a common sequence of initial arguments, we can use [^BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS] to automatically wrap the first three of the [^def]s and manually wrap just the last. Here's @@ -1159,10 +1163,10 @@ Then... .def("f", fx1, xf_overloads()); .def("f", fx2) -[endpage] -[endpage] [/ Functions ] +[endsect] +[endsect] [/ Functions ] -[beginpage:object Object Interface] +[section:object 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., @@ -1175,9 +1179,9 @@ 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. -[$images/python.png] +[$../images/python.png] -[beginpage Basic Interface] +[section Basic Interface] Class [^object] wraps [^PyObject*]. All the intricacies of dealing with [^PyObject]s such as managing reference counting are handled by the @@ -1213,8 +1217,8 @@ 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. -[endpage] -[beginpage Derived Object types] +[endsect] +[section Derived Object types] Boost.Python comes with a set of derived [^object] types corresponding to that of Python's: @@ -1292,8 +1296,8 @@ We can use this to create wrapped instances. Example: assert(vec345.attr("length") == 5.0); -[endpage] -[beginpage Extracting C++ objects] +[endsect] +[section 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: @@ -1330,8 +1334,8 @@ facility in fact solves the mutable copying problem: d['whatever'] = 3; # modifies x.__dict__ ! -[endpage] -[beginpage Enums] +[endsect] +[section 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 @@ -1354,10 +1358,10 @@ 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 +[blurb __note__ [*what is a scope?]\n\n 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].] +attributes. Details can be found [@../../../../v2/scope.html here].] You can access those values in Python as @@ -1392,10 +1396,10 @@ create a new scope around a class: [def PyModule_New [@http://www.python.org/doc/current/api/moduleObjects.html#l2h-591 PyModule_New]] [def PyModule_GetDict [@http://www.python.org/doc/current/api/moduleObjects.html#l2h-594 PyModule_GetDict]] -[endpage] -[endpage] [/ Object Interface] +[endsect] +[endsect] [/ Object Interface] -[beginpage Embedding] +[section Embedding] By now you should know how to use Boost.Python to call your C++ code from Python. However, sometimes you may need to do the reverse: call Python code @@ -1418,7 +1422,7 @@ Boost.Python's static link library comes in two variants. Both are located in Boost's [^/libs/python/build/bin-stage] subdirectory. On Windows, the variants are called [^boost_python.lib] (for release builds) and [^boost_python_debug.lib] (for debugging). If you can't find the libraries, -you probably haven't built Boost.Python yet. See [@../../building.html +you probably haven't built Boost.Python yet. See [@../../../../building.html Building and Testing] on how to do this. Python's static link library can be found in the [^/libs] subdirectory of @@ -1453,11 +1457,11 @@ Being able to build is nice, but there is nothing to build yet. Embedding the Python interpreter into one of your C++ programs requires these 4 steps: -# '''#include''' [^][br][br] +# '''#include''' [^]\n\n -# Call Py_Initialize() to start the interpreter and create the [^__main__] module.[br][br] +# Call Py_Initialize() to start the interpreter and create the [^__main__] module.\n\n -# Call other Python C API routines to use the interpreter.[br][br] +# Call other Python C API routines to use the interpreter.\n\n # Call Py_Finalize() to stop the interpreter and release its resources. @@ -1465,7 +1469,7 @@ steps: [:['[*Now that we can embed the interpreter in our programs, lets see how to put it to use...]]] -[beginpage Using the interpreter] +[section Using the interpreter] As you probably already know, objects in Python are reference-counted. Naturally, the [^PyObject]s of the Python/C API are also reference-counted. @@ -1474,7 +1478,7 @@ automatic in Python, the Python/C API requires you to do it [@http://www.python.org/doc/current/api/refcounts.html by hand]. This is messy and especially hard to get right in the presence of C++ exceptions. Fortunately Boost.Python provides the [@../../v2/handle.html handle] and -[@../../v2/object.html object] class templates to automate the process. +[@../../../../v2/object.html object] class templates to automate the process. [h2 Reference-counting handles and objects] @@ -1486,7 +1490,7 @@ be 'handled' by Boost.Python. For a function returning a ['borrowed reference] we'll have to tell the [^handle] that the [^PyObject*] is borrowed with the aptly named -[@../../v2/handle.html#borrowed-spec borrowed] function. Two functions +[@../../../../v2/handle.html#borrowed-spec borrowed] function. Two functions returning borrowed references are PyImport_AddModule and PyModule_GetDict. The former returns a reference to an already imported module, the latter retrieves a module's namespace dictionary. Let's use them to retrieve the @@ -1502,8 +1506,8 @@ out of the raw [^PyObject*] without wrapping it in a call to borrowed. One such function that returns a new reference is PyRun_String which we'll discuss in the next section. -[blurb __detail__ [*Handle is a class ['template], so why haven't we been using any template parameters?][br] -[br] +[blurb __note__ [*Handle is a class ['template], so why haven't we been using any template parameters?]\n +\n [^handle] has a single template parameter specifying the type of the managed object. This type is [^PyObject] 99% of the time, so the parameter was defaulted to [^PyObject] for convenience. Therefore we can use the shorthand [^handle<>] instead of the longer, but equivalent, [^handle]. ] @@ -1576,7 +1580,7 @@ you want to be a Dr. Frankenstein, always wrap [^PyObject*]s in [^handle]s. It's nice that [^handle] manages the reference counting details for us, but other than that it doesn't do much. Often we'd like to have a more useful class to manipulate Python objects. But we have already seen such a class -above, and in the [@object_interface.html previous section]: the aptly +above, and in the [@object.html previous section]: the aptly named [^object] class and it's derivatives. We've already seen that they can be constructed from a [^handle]. The following examples should further illustrate this fact: @@ -1618,7 +1622,11 @@ perform. [h2 Exception handling] -If an exception occurs in the execution of some Python code, the PyRun_String function returns a null pointer. Constructing a [^handle] out of this null pointer throws [@../../v2/errors.html#error_already_set-spec error_already_set], so basically, the Python exception is automatically translated into a C++ exception when using [^handle]: +If an exception occurs in the execution of some Python code, the PyRun_String +function returns a null pointer. Constructing a [^handle] out of this null +pointer throws [@../../../../v2/errors.html#error_already_set-spec error_already_set], +so basically, the Python exception is automatically translated into a +C++ exception when using [^handle]: try { @@ -1637,7 +1645,14 @@ If an exception occurs in the execution of some Python code, the PyRun_String fu // handle the exception in some way } -The [^error_already_set] exception class doesn't carry any information in itself. To find out more about the Python exception that occurred, you need to use the [@http://www.python.org/doc/api/exceptionHandling.html exception handling functions] of the Python/C API in your catch-statement. This can be as simple as calling [@http://www.python.org/doc/api/exceptionHandling.html#l2h-70 PyErr_Print()] to print the exception's traceback to the console, or comparing the type of the exception with those of the [@http://www.python.org/doc/api/standardExceptions.html standard exceptions]: +The [^error_already_set] exception class doesn't carry any information in itself. +To find out more about the Python exception that occurred, you need to use the +[@http://www.python.org/doc/api/exceptionHandling.html exception handling functions] +of the Python/C API in your catch-statement. This can be as simple as calling +[@http://www.python.org/doc/api/exceptionHandling.html#l2h-70 PyErr_Print()] to +print the exception's traceback to the console, or comparing the type of the +exception with those of the [@http://www.python.org/doc/api/standardExceptions.html +standard exceptions]: catch(error_already_set) { @@ -1652,9 +1667,12 @@ The [^error_already_set] exception class doesn't carry any information in itself } } -(To retrieve even more information from the exception you can use some of the other exception handling functions listed [@http://www.python.org/doc/api/exceptionHandling.html here].) +(To retrieve even more information from the exception you can use some of the other +exception handling functions listed [@http://www.python.org/doc/api/exceptionHandling.html here].) -If you'd rather not have [^handle] throw a C++ exception when it is constructed, you can use the [@../../v2/handle.html#allow_null-spec allow_null] function in the same way you'd use borrowed: +If you'd rather not have [^handle] throw a C++ exception when it is constructed, you +can use the [@../../v2/handle.html#allow_null-spec allow_null] function in the same +way you'd use borrowed: handle<> result((allow_null(PyRun_String( "5/0" @@ -1667,10 +1685,10 @@ If you'd rather not have [^handle] throw a C++ exception when it is constructed, else // everything went okay, it's safe to use the result -[endpage] -[endpage] [/ Embedding] +[endsect] +[endsect] [/ Embedding] -[beginpage Iterators] +[section Iterators] In C++, and STL in particular, we see iterators everywhere. Python also has iterators, but these are two very different beasts. @@ -1744,8 +1762,8 @@ Now, our C++ Wrapper: .property("pions", range(&F::p_begin, &F::p_end)) .property("bogons", range(&F::b_begin, &F::b_end)); -[endpage] -[beginpage:exception Exception Translation] +[endsect] +[section:exception 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 @@ -1765,12 +1783,12 @@ Users may provide custom translation. Here's an example: PodBayDoorException>(translator); ... -[endpage] -[beginpage:techniques General Techniques] +[endsect] +[section:techniques General Techniques] Here are presented some useful techniques that you can use while wrapping code with Boost.Python. -[beginpage Creating Packages] +[section Creating Packages] A Python package is a collection of modules that provide to the user a certain functionality. If you're not familiar on how to create packages, a good @@ -1841,7 +1859,8 @@ actually a Python package. It can be a empty file, but can also perform some magic, that will be shown later. Now our package is ready. All the user has to do is put [^sounds] into his -[@http://www.python.org/doc/current/tut/node8.html#SECTION008110000000000000000 PYTHONPATH] and fire up the interpreter: +[@http://www.python.org/doc/current/tut/node8.html#SECTION008110000000000000000 PYTHONPATH] +and fire up the interpreter: >>> import sounds.io >>> import sounds.filters @@ -1928,8 +1947,8 @@ from the [^filters] package: >>> import sounds.filters >>> sounds.filters.echo_noise(...) -[endpage] -[beginpage Extending Wrapped Objects in Python] +[endsect] +[section Extending Wrapped Objects in Python] Thanks to Python's flexibility, you can easily add new methods to a class, even after it was already created: @@ -1961,7 +1980,8 @@ we have a class [^point] in C++: } If we are using the technique from the previous session, -[@creating_packages.html Creating Packages], we can code directly into [^geom/__init__.py]: +[@techniques.html#creating_packages Creating Packages], we can code directly +into [^geom/__init__.py]: from _geom import * @@ -2022,8 +2042,8 @@ many overloads and/or arguments this is often a great simplification, again with virtually zero memory footprint and zero compile-time overhead for the keyword support. -[endpage] -[beginpage Reducing Compiling Time] +[endsect] +[section Reducing Compiling Time] If you have ever exported a lot of classes, you know that it takes quite a good time to compile the Boost.Python wrappers. Plus the memory consumption can @@ -2079,15 +2099,15 @@ This method is recommended too if you are developing the C++ library and exporting it to Python at the same time: changes in a class will only demand the compilation of a single cpp, instead of the entire wrapper code. -[blurb __note__ If you're exporting your classes with [@../../../pyste/index.html Pyste], +[blurb __note__ If you're exporting your classes with [@../../../../../pyste/index.html Pyste], take a look at the [^--multiple] option, that generates the wrappers in various files as demonstrated here.] [blurb __note__ This method is useful too if you are getting the error message ['"fatal error C1204:Compiler limit:internal structure overflow"] when compiling -a large source file, as explained in the [@../../v2/faq.html#c1204 FAQ].] +a large source file, as explained in the [@../../../../v2/faq.html#c1204 FAQ].] -[endpage] -[endpage] [/ General Techniques] +[endsect] +[endsect] [/ General Techniques] diff --git a/doc/tutorial/doc/tutorial.qbk.xml b/doc/tutorial/doc/tutorial.qbk.xml deleted file mode 100644 index 5e5e684e..00000000 --- a/doc/tutorial/doc/tutorial.qbk.xml +++ /dev/null @@ -1,2593 +0,0 @@ - - - - - - Joelde Guzman - - - David Abrahams - - - - 2002 - 2003 - 2004 - Joel de Guzman, David Abrahams - - - - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) - - - - - Reflects C++ classes and functions into Python - - - - - Boost.Boost Python 1.0 - - - -
-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). -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 <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...
-
- Building Hello World -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. - - - - - - - Building without bjam - - - - - - - -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. - -Take note however that the preferred build tool for Boost.Python is bjam. -There are so many ways to set up the build incorrectly. Experience shows -that 90% of the "I can't build Boost.Python" problems come from people -who had to use a different tool. -] - -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: - - -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 -here. -The complete list of bjam pre-built -executables can be found -here. -Let's Jam! - - -Here is our minimalist Jamfile: - subproject libs/python/example/tutorial ; -[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 - <dll>../../build/boost_python# dependencies - ; - - - 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 - ; - - - 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 - ; -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. 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: - - -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!
-
- 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' - - -
-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. -
-
-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, assuming we have placed our Var class inside the namespace -hello as we did before: - - - >>> x = hello.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 -
-
-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", &Num::get) - .add_property("value", &Num::get, &Num::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", &Num::get) - -
-
-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: - - -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<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>()); - -
-
-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; - }; - - - struct BaseWrap : Base - { - BaseWrap(PyObject* self_) - : self(self_) {} - BaseWrap(PyObject* self_, Base const& copy) - : Base(copy), self(self_) {} - int f() { return call_method<int>(self, "f"); } - int default_f() { return Base::f(); } // <<=== ***ADDED*** - PyObject* self; - }; - - - - - - - - member function and methods - - 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. - - - - - - - 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() - RuntimeError: This class cannot be instantiated from Python - - -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. -
-
-Deriving a Python Class - -Continuing, we can derive from our base class Base in Python and override -the virtual function in Python. Before we can do that, we have to set up -our class_ wrapper as: - - - class_<Base, BaseWrap, boost::noncopyable>("Base") - ; - - -Otherwise, we have to suppress the Base class' no_init by adding an -__init__() method to all our derived classes. no_init actually adds -an __init__ method that raises a Python RuntimeError exception. - - - >>> 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<int>(self, "f"); dispatches the call back to Python. - -def f(self): return 42 is finally called. - -
-
-Virtual Functions with Default Implementations - -Recall that in the -previous section, we -wrapped a class with a pure virtual function that we then implemented in -C++ or Python classes derived from it. Our base class: - - - struct Base - { - virtual int f() = 0; - }; - - -had a pure virtual function f. If, however, its member function f was -not declared as pure virtual: - - - struct Base - { - virtual int f() { return 0; } - }; - - -and instead had a default implementation that returns 0, as shown above, -we need to add a forwarding function that calls the Base default virtual -function f implementation: - - - struct BaseWrap : Base - { - BaseWrap(PyObject* self_) - : self(self_) {} - int f() { return call_method<int>(self, "f"); } - int default_f() { return Base::f(); } // <<=== ***ADDED*** - PyObject* self; - }; - - -Then, Boost.Python needs to keep track of 1) the dispatch function f and -2) the forwarding function to its default implementation default_f. -There's a special def function for this purpose. Here's how it is -applied to our example above: - - - class_<Base, BaseWrap, BaseWrap, boost::noncopyable>("Base") - .def("f", &Base::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 - -
-
-Class Operators/Special Functions -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>("FilePos") - .def(self + int()) // __add__ - .def(int() + self) // __radd__ - .def(self - self) // __sub__ - .def(self - int()) // __sub__ - .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". -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_<Rational>() - .def(float_(self)) // __float__ - .def(pow(self, other<Rational>)) // __pow__ - .def(abs(self)) // __abs__ - .def(str(self)) // __str__ - ; - - -Need we say more? - - - - - - - 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)). - - - - -
-
-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! - -
-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! - -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<args..., - policy2<args..., - policy3<args...> > > - - -Here is the list of predefined call policies. A complete reference detailing -these can be found -here. - - -with_custodian_and_ward - Ties lifetimes of the arguments - -with_custodian_and_ward_postcall - Ties lifetimes of the arguments and results - -return_internal_reference - Ties lifetime of one argument to that of result - -return_value_policy<T> with T one of: - - -reference_existing_object -naive (dangerous) approach - -copy_const_reference -Boost.Python v1 approach - -copy_non_const_reference - - -manage_new_object - Adopt a pointer and hold the instance - - - - - - - - Remember the Zen, Luke: - - -"Explicit is better than implicit" - -"In the face of ambiguity, refuse the temptation to guess" - - - - - -
-
-Overloading - -The following illustrates a scheme for manually wrapping an overloaded -member functions. Of course, the same technique can be applied to wrapping -overloaded non-member functions. - -We have here our C++ class: - - - struct X - { - bool f(int a) - { - return true; - } - - bool f(int a, double b) - { - return true; - } - - bool f(int a, double b, char c) - { - return true; - } - - int f(int a, int b, int c) - { - return a + b + c; - }; - }; - - -Class X has 4 overloaded functions. We shall start by introducing some -member function pointer variables: - - - bool (X::*fx1)(int) = &X::f; - bool (X::*fx2)(int, double) = &X::f; - bool (X::*fx3)(int, double, char)= &X::f; - int (X::*fx4)(int, int, int) = &X::f; - - -With these in hand, we can proceed to define and wrap this for Python: - - - .def("f", fx1) - .def("f", fx2) - .def("f", fx3) - .def("f", fx4) - -
-
-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, we had to resort to manual -wrapping as outlined in the -previous section, or -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_FUNCTION_OVERLOADS -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()); - -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS -Objects here, objects there, objects here there everywhere. More frequently -than anything else, we need to expose member functions of our classes to -Python. Then again, we have the same inconveniences as before when default -arguments or overloads with a common sequence of initial arguments come -into play. Another macro is provided to make this a breeze. - -Like BOOST_PYTHON_FUNCTION_OVERLOADS, -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS may be used to automatically create -the thin wrappers for wrapping member functions. Let's have an example: - - - struct george - { - void - wack_em(int a, int b = 0, char c = 'x') - { - /*...*/ - } - }; - - -The macro invocation: - - - BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(george_overloads, wack_em, 1, 3) - - -will generate a set of thin wrappers for george's wack_em member function -accepting a minimum of 1 and a maximum of 3 arguments (i.e. the third and -fourth macro argument). The thin wrappers are all enclosed in a class named -george_overloads that can then be used as an argument to def(...): - - - .def("wack_em", &george::wack_em, george_overloads()); - - -See the -overloads reference -for details. -init and optional -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). -
-
-Auto-Overloading - -It was mentioned in passing in the previous section that -BOOST_PYTHON_FUNCTION_OVERLOADS and BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS -can also be used for overloaded functions and member functions with a -common sequence of initial arguments. Here is an example: - - - void foo() - { - /*...*/ - } - - void foo(bool a) - { - /*...*/ - } - - void foo(bool a, int b) - { - /*...*/ - } - - void foo(bool a, int b, char c) - { - /*...*/ - } - - -Like in the previous section, we can generate thin wrappers for these -overloaded functions in one-shot: - - - BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3) - - -Then... - - - .def("foo", foo, foo_overloads()); - - -Notice though that we have a situation now where we have a minimum of zero -(0) arguments and a maximum of 3 arguments. -Manual Wrapping -It is important to emphasize however that the overloaded functions must -have a common sequence of initial arguments. Otherwise, our scheme above -will not work. If this is not the case, we have to wrap our functions - -manually. - -Actually, we can mix and match manual wrapping of overloaded functions and -automatic wrapping through BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS and -its sister, BOOST_PYTHON_FUNCTION_OVERLOADS. Following up on our example -presented in the section -on overloading, since the -first 4 overload functins have a common sequence of initial arguments, we -can use BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS to automatically wrap the -first three of the defs and manually wrap just the last. Here's -how we'll do this: - - - BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xf_overloads, f, 1, 4) - - -Create a member function pointers as above for both X::f overloads: - - - bool (X::*fx1)(int, double, char) = &X::f; - int (X::*fx2)(int, int, int) = &X::f; - - -Then... - - - .def("f", fx1, xf_overloads()); - .def("f", fx2) - -
-
- 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++ objects are as close as possible to Python. This -should minimize the learning curve significantly. - - - -
-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, 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. -
-
-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 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 - -class_<T> as objects -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); - -
-
-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 the mutable copying problem: - - - dict d = extract<dict>(x.attr("__dict__")); - d['whatever'] = 3;# modifies x.__dict__ ! - -
-
-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>("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. - - - - - - - what is a scope? - - 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 -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>("X") - .def( ... ) - .def( ... ) - ; - - // Expose X::nested as X.nested - enum_<X::nested>("nested") - .value("red", red) - .value("blue", blue) - ; - -
-
-Embedding - -By now you should know how to use Boost.Python to call your C++ code from -Python. However, sometimes you may need to do the reverse: call Python code -from the C++-side. This requires you to embed the Python interpreter -into your C++ program. - -Currently, Boost.Python does not directly support everything you'll need -when embedding. Therefore you'll need to use the - -Python/C API to fill in -the gaps. However, Boost.Python already makes embedding a lot easier and, -in a future version, it may become unnecessary to touch the Python/C API at -all. So stay tuned... -Building embedded programs -To be able to use embedding in your programs, they have to be linked to -both Boost.Python's and Python's static link library. - -Boost.Python's static link library comes in two variants. Both are located -in Boost's /libs/python/build/bin-stage subdirectory. On Windows, the -variants are called boost_python.lib (for release builds) and -boost_python_debug.lib (for debugging). If you can't find the libraries, -you probably haven't built Boost.Python yet. See -and Testing on how to do this. - -Python's static link library can be found in the /libs subdirectory of -your Python directory. On Windows it is called pythonXY.lib where X.Y is -your major Python version number. - -Additionally, Python's /include subdirectory has to be added to your -include path. - -In a Jamfile, all the above boils down to: - projectroot c:\projects\embedded_program ; # location of the program -[pre - projectroot c:\projects\embedded_program ; # location of the program - - -# bring in the rules for python - SEARCH on python.jam =#(BOOST_BUILD_PATH) ; - include python.jam ; - - exe embedded_program# name of the executable - : #sources - embedded_program.cpp - :# requirements - <find-library>boost_python <library-path>c:\boost\libs\python#(PYTHON_PROPERTIES) - <library-path>#(PYTHON_LIB_PATH) - <find-library>#(PYTHON_EMBEDDED_LIBRARY) ; - - - # bring in the rules for python - SEARCH on python.jam = $(BOOST_BUILD_PATH) ; - include python.jam ; - - - exe embedded_program# name of the executable - : #sources - embedded_program.cpp - :# requirements - <find-library>boost_python <library-path>c:\boost\libs\python#(PYTHON_PROPERTIES) - <library-path>#(PYTHON_LIB_PATH) - <find-library>#(PYTHON_EMBEDDED_LIBRARY) ; - - - exe embedded_program # name of the executable - : #sources - embedded_program.cpp - : # requirements - <find-library>boost_python <library-path>c:\boost\libs\python - $(PYTHON_PROPERTIES) - <library-path>$(PYTHON_LIB_PATH) - <find-library>$(PYTHON_EMBEDDED_LIBRARY) ; -] -Getting started -Being able to build is nice, but there is nothing to build yet. Embedding -the Python interpreter into one of your C++ programs requires these 4 -steps: - - -#include <boost/python.hpp> - - - -Call -Py_Initialize() to start the interpreter and create the __main__ module. - - - -Call other Python C API routines to use the interpreter. - - - -Call -Py_Finalize() to stop the interpreter and release its resources. - - -(Of course, there can be other C++ code between all of these steps.) -
Now that we can embed the interpreter in our programs, lets see how to put it to use...
-
-Using the interpreter - -As you probably already know, objects in Python are reference-counted. -Naturally, the PyObjects of the Python/C API are also reference-counted. -There is a difference however. While the reference-counting is fully -automatic in Python, the Python/C API requires you to do it - -by hand. This is -messy and especially hard to get right in the presence of C++ exceptions. -Fortunately Boost.Python provides the -handle and - -object class templates to automate the process. -Reference-counting handles and objects -There are two ways in which a function in the Python/C API can return a -PyObject*: as a borrowed reference or as a new reference. Which of -these a function uses, is listed in that function's documentation. The two -require slightely different approaches to reference-counting but both can -be 'handled' by Boost.Python. - -For a function returning a borrowed reference we'll have to tell the -handle that the PyObject* is borrowed with the aptly named - -borrowed function. Two functions -returning borrowed references are -PyImport_AddModule and -PyModule_GetDict. -The former returns a reference to an already imported module, the latter -retrieves a module's namespace dictionary. Let's use them to retrieve the -namespace of the __main__ module: - - - object main_module(( - handle<>(borrowed(PyImport_AddModule("__main__"))))); - - object main_namespace = main_module.attr("__dict__"); - - -For a function returning a new reference we can just create a handle -out of the raw PyObject* without wrapping it in a call to borrowed. One -such function that returns a new reference is -PyRun_String which we'll -discuss in the next section. - - - - - - - Handle is a class template, so why haven't we been using any template parameters? - - - -handle has a single template parameter specifying the type of the managed object. This type is PyObject 99% of the time, so the parameter was defaulted to PyObject for convenience. Therefore we can use the shorthand handle<> instead of the longer, but equivalent, handle<PyObject>. - - - - - -Running Python code -To run Python code from C++ there is a family of functions in the API -starting with the PyRun prefix. You can find the full list of these -functions -here. They -all work similarly so we will look at only one of them, namely: - - - PyObject* PyRun_String(char *str, int start, PyObject *globals, PyObject *locals) - - - -PyRun_String takes the code to execute as a null-terminated (C-style) -string in its str parameter. The function returns a new reference to a -Python object. Which object is returned depends on the start paramater. - -The start parameter is the start symbol from the Python grammar to use -for interpreting the code. The possible values are: - -Start symbols - - - -Py_eval_inputfor interpreting isolated expressions - -Py_file_inputfor interpreting sequences of statements - -Py_single_inputfor interpreting a single statement - - - - -When using -Py_eval_input, the input string must contain a single expression -and its result is returned. When using -Py_file_input, the string can -contain an abitrary number of statements and None is returned. - -Py_single_input works in the same way as -Py_file_input but only accepts a -single statement. - -Lastly, the globals and locals parameters are Python dictionaries -containing the globals and locals of the context in which to run the code. -For most intents and purposes you can use the namespace dictionary of the -__main__ module for both parameters. - -We have already seen how to get the __main__ module's namespace so let's -run some Python code in it: - - - object main_module(( - handle<>(borrowed(PyImport_AddModule("__main__"))))); - - object main_namespace = main_module.attr("__dict__"); - - handle<> ignored((PyRun_String( - - "hello = file('hello.txt', 'w')\n" - "hello.write('Hello world!')\n" - "hello.close()" - - , Py_file_input - , main_namespace.ptr() - , main_namespace.ptr()) - )); - - -Because the Python/C API doesn't know anything about objects, we used -the object's ptr member function to retrieve the PyObject*. - -This should create a file called 'hello.txt' in the current directory -containing a phrase that is well-known in programming circles. - - Note that we wrap the return value of -PyRun_String in a -(nameless) handle even though we are not interested in it. If we didn't -do this, the the returned object would be kept alive unnecessarily. Unless -you want to be a Dr. Frankenstein, always wrap PyObject*s in handles. -Beyond handles -It's nice that handle manages the reference counting details for us, but -other than that it doesn't do much. Often we'd like to have a more useful -class to manipulate Python objects. But we have already seen such a class -above, and in the -previous section: the aptly -named object class and it's derivatives. We've already seen that they -can be constructed from a handle. The following examples should further -illustrate this fact: - - - object main_module(( - handle<>(borrowed(PyImport_AddModule("__main__"))))); - - object main_namespace = main_module.attr("__dict__"); - - handle<> ignored((PyRun_String( - - "result = 5 ** 2" - - , Py_file_input - , main_namespace.ptr() - , main_namespace.ptr()) - )); - - int five_squared = extract<int>(main_namespace["result"]); - - -Here we create a dictionary object for the __main__ module's namespace. -Then we assign 5 squared to the result variable and read this variable from -the dictionary. Another way to achieve the same result is to let - -PyRun_String return the result directly with -Py_eval_input: - - - object result((handle<>( - PyRun_String("5 ** 2" - , Py_eval_input - , main_namespace.ptr() - , main_namespace.ptr())) - )); - - int five_squared = extract<int>(result); - - - Note that object's member function to return the wrapped -PyObject* is called ptr instead of get. This makes sense if you -take into account the different functions that object and handle -perform. -Exception handling -If an exception occurs in the execution of some Python code, the -PyRun_String function returns a null pointer. Constructing a handle out of this null pointer throws -error_already_set, so basically, the Python exception is automatically translated into a C++ exception when using handle: - - - try - { - object result((handle<>(PyRun_String( - "5/0" - , Py_eval_input - , main_namespace.ptr() - , main_namespace.ptr())) - )); - - // execution will never get here: - int five_divided_by_zero = extract<int>(result); - } - catch(error_already_set) - { - // handle the exception in some way - } - - -The error_already_set exception class doesn't carry any information in itself. To find out more about the Python exception that occurred, you need to use the -exception handling functions of the Python/C API in your catch-statement. This can be as simple as calling -PyErr_Print() to print the exception's traceback to the console, or comparing the type of the exception with those of the -standard exceptions: - - - catch(error_already_set) - { - if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError)) - { - // handle ZeroDivisionError specially - } - else - { - // print all other errors to stderr - PyErr_Print(); - } - } - - -(To retrieve even more information from the exception you can use some of the other exception handling functions listed -here.) - -If you'd rather not have handle throw a C++ exception when it is constructed, you can use the -allow_null function in the same way you'd use borrowed: - - - handle<> result((allow_null(PyRun_String( - "5/0" - , Py_eval_input - , main_namespace.ptr() - , main_namespace.ptr())))); - - if (!result) - // Python exception occurred - else - // everything went okay, it's safe to use the result - - -
-
-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<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: - - -range(start, finish) - -range<Policies,Target>(start, finish) - - -Here, start/finish may be one of: - - -member data pointers - -member function pointers - -adaptable function object (use Target parameter) - - -iterator - - -iterator<T, Policies>() - - -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)); - -
-
- 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 const& x) { - PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave..."); - } - BOOST_PYTHON_MODULE(kubrick) { - register_exception_translator< - PodBayDoorException>(translator); - ... - -
-
- General Techniques - -Here are presented some useful techniques that you can use while wrapping code with Boost.Python. - -
-Creating Packages - -A Python package is a collection of modules that provide to the user a certain -functionality. If you're not familiar on how to create packages, a good -introduction to them is provided in the - -Python Tutorial. - -But we are wrapping C++ code, using Boost.Python. How can we provide a nice -package interface to our users? To better explain some concepts, let's work -with an example. - -We have a C++ library that works with sounds: reading and writing various -formats, applying filters to the sound data, etc. It is named (conveniently) -sounds. Our library already has a neat C++ namespace hierarchy, like so: - - - sounds::core - sounds::io - sounds::filters - - -We would like to present this same hierarchy to the Python user, allowing him -to write code like this: - - - import sounds.filters - sounds.filters.echo(...)# echo is a C++ function - - -The first step is to write the wrapping code. We have to export each module -separately with Boost.Python, like this: - - - /* file core.cpp */ - BOOST_PYTHON_MODULE(core) - { - /* export everything in the sounds::core namespace */ - ... - } - - /* file io.cpp */ - BOOST_PYTHON_MODULE(io) - { - /* export everything in the sounds::io namespace */ - ... - } - - /* file filters.cpp */ - BOOST_PYTHON_MODULE(filters) - { - /* export everything in the sounds::filters namespace */ - ... - } - - -Compiling these files will generate the following Python extensions: -core.pyd, io.pyd and filters.pyd. - - - - - - - The extension .pyd is used for python extension modules, which -are just shared libraries. Using the default for your system, like .so for -Unix and .dll for Windows, works just as well. - - - - - -Now, we create this directory structure for our Python package: - sounds/ - __init__.py - core.pyd - filters.pyd - io.pyd - -The file __init__.py is what tells Python that the directory sounds/ is -actually a Python package. It can be a empty file, but can also perform some -magic, that will be shown later. - -Now our package is ready. All the user has to do is put sounds into his - -PYTHONPATH and fire up the interpreter: - - - >>> import sounds.io - >>> import sounds.filters - >>> sound = sounds.io.open('file.mp3') - >>> new_sound = sounds.filters.echo(sound, 1.0) - - -Nice heh? - -This is the simplest way to create hierarchies of packages, but it is not very -flexible. What if we want to add a pure Python function to the filters -package, for instance, one that applies 3 filters in a sound object at once? -Sure, you can do this in C++ and export it, but why not do so in Python? You -don't have to recompile the extension modules, plus it will be easier to write -it. - -If we want this flexibility, we will have to complicate our package hierarchy a -little. First, we will have to change the name of the extension modules: - - - /* file core.cpp */ - BOOST_PYTHON_MODULE(_core) - { - ... - /* export everything in the sounds::core namespace */ - } - - -Note that we added an underscore to the module name. The filename will have to -be changed to _core.pyd as well, and we do the same to the other extension modules. -Now, we change our package hierarchy like so: - sounds/ - __init__.py - core/ - __init__.py - _core.pyd - filters/ - __init__.py - _filters.pyd - io/ - __init__.py - _io.pyd - -Note that we created a directory for each extension module, and added a -__init__.py to each one. But if we leave it that way, the user will have to -access the functions in the core module with this syntax: - - - >>> import sounds.core._core - >>> sounds.core._core.foo(...) - - -which is not what we want. But here enters the __init__.py magic: everything -that is brought to the __init__.py namespace can be accessed directly by the -user. So, all we have to do is bring the entire namespace from _core.pyd -to core/__init__.py. So add this line of code to sounds/core/__init__.py: - - - from _core import * - - -We do the same for the other packages. Now the user accesses the functions and -classes in the extension modules like before: - - - >>> import sounds.filters - >>> sounds.filters.echo(...) - - -with the additional benefit that we can easily add pure Python functions to -any module, in a way that the user can't tell the difference between a C++ -function and a Python function. Let's add a pure Python function, -echo_noise, to the filters package. This function applies both the -echo and noise filters in sequence in the given sound object. We -create a file named sounds/filters/echo_noise.py and code our function: - - - import _filters - def echo_noise(sound): - s = _filters.echo(sound) - s = _filters.noise(sound) - return s - - -Next, we add this line to sounds/filters/__init__.py: - - - from echo_noise import echo_noise - - -And that's it. The user now accesses this function like any other function -from the filters package: - - - >>> import sounds.filters - >>> sounds.filters.echo_noise(...) - -
-
-Extending Wrapped Objects in Python - -Thanks to Python's flexibility, you can easily add new methods to a class, -even after it was already created: - - - >>> class C(object): pass - >>> - >>># a regular function - >>> def C_str(self): return 'A C instance!' - >>> - >>># now we turn it in a member function - >>> C.__str__ = C_str - >>> - >>> c = C() - >>> print c - A C instance! - >>> C_str(c) - A C instance! - - -Yes, Python rox. - -We can do the same with classes that were wrapped with Boost.Python. Suppose -we have a class point in C++: - - - class point {...}; - - BOOST_PYTHON_MODULE(_geom) - { - class_<point>("point")...; - } - - -If we are using the technique from the previous session, - -Creating Packages, we can code directly into geom/__init__.py: - - - from _geom import *# a regular function - def point_str(self): - return str((self.x, self.y))# now we turn it into a member function - point.__str__ = point_str - - -All point instances created from C++ will also have this member function! -This technique has several advantages: - - -Cut down compile times to zero for these additional functions - -Reduce the memory footprint to virtually zero - -Minimize the need to recompile - -Rapid prototyping (you can move the code to C++ if required without changing the interface) - - -You can even add a little syntactic sugar with the use of metaclasses. Let's -create a special metaclass that "injects" methods in other classes. - - -# The one Boost.Python uses for all wrapped classes.# You can use here any class exported by Boost instead of "point" - BoostPythonMetaclass = point.__class__ - - class injector(object): - class __metaclass__(BoostPythonMetaclass): - def __init__(self, name, bases, dict): - for b in bases: - if type(b) not in (self, type): - for k,v in dict.items(): - setattr(b,k,v) - return type.__init__(self, name, bases, dict)# inject some methods in the point foo - class more_point(injector, point): - def __repr__(self): - return 'Point(x=%s, y=%s)' % (self.x, self.y) - def foo(self): - print 'foo!' - - -Now let's see how it got: - - - >>> print point() - Point(x=10, y=10) - >>> point().foo() - foo! - - -Another useful idea is to replace constructors with factory functions: - - - _point = point - - def point(x=0, y=0): - return _point(x, y) - - -In this simple case there is not much gained, but for constructurs with -many overloads and/or arguments this is often a great simplification, again -with virtually zero memory footprint and zero compile-time overhead for -the keyword support. -
-
-Reducing Compiling Time - -If you have ever exported a lot of classes, you know that it takes quite a good -time to compile the Boost.Python wrappers. Plus the memory consumption can -easily become too high. If this is causing you problems, you can split the -class_ definitions in multiple files: - - - /* file point.cpp */ - #include <point.h> - #include <boost/python.hpp> - - void export_point() - { - class_<point>("point")...; - } - - /* file triangle.cpp */ - #include <triangle.h> - #include <boost/python.hpp> - - void export_triangle() - { - class_<triangle>("triangle")...; - } - - -Now you create a file main.cpp, which contains the BOOST_PYTHON_MODULE -macro, and call the various export functions inside it. - - - void export_point(); - void export_triangle(); - - BOOST_PYTHON_MODULE(_geom) - { - export_point(); - export_triangle(); - } - - -Compiling and linking together all this files produces the same result as the -usual approach: - - - #include <boost/python.hpp> - #include <point.h> - #include <triangle.h> - - BOOST_PYTHON_MODULE(_geom) - { - class_<point>("point")...; - class_<triangle>("triangle")...; - } - - -but the memory is kept under control. - -This method is recommended too if you are developing the C++ library and -exporting it to Python at the same time: changes in a class will only demand -the compilation of a single cpp, instead of the entire wrapper code. - - - - - - - If you're exporting your classes with -Pyste, -take a look at the --multiple option, that generates the wrappers in -various files as demonstrated here. - - - - - - - - - - - This method is useful too if you are getting the error message -"fatal error C1204:Compiler limit:internal structure overflow" when compiling -a large source file, as explained in the -FAQ. - - - - -
-
- diff --git a/doc/tutorial/doc/tutorial.xml b/doc/tutorial/doc/tutorial.xml new file mode 100644 index 00000000..7b8a5d86 --- /dev/null +++ b/doc/tutorial/doc/tutorial.xml @@ -0,0 +1,2668 @@ + + + + + + Joel + de Guzman + + + David + Abrahams + + + + 2002 + 2003 + 2004 + Joel de Guzman, David Abrahams + + + + + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + + http://www.boost.org/LICENSE_1_0.txt + ) + + + + + + Reflects C++ classes and functions into Python + + + + + + + python 1.0 + + + +
+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). +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 <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...
+
+ Building Hello World +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. + + + + + + + Building without bjam + + + 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. + + + Take note however that the preferred build tool for Boost.Python is bjam. + There are so many ways to set up the build incorrectly. Experience shows + that 90% of the "I can't build Boost.Python" problems come from people + who had to use a different tool. + + + + + + +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: + + +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. The complete list of Bjam executables can be found +here. +Let's Jam! + + +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 + ; +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. + + + + + + + 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: + + +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!
+
+ 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' + + + +
+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. +
+
+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, assuming we have placed our Var class inside the namespace +hello as we did before: + + +>>> x = hello.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 +
+
+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", &Num::get) + .add_property("value", &Num::get, &Num::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", &Num::get) + + +
+
+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: + + +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<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>()); + + +
+
+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; +}; + + +struct BaseWrap : Base +{ + BaseWrap(PyObject* self_) + : self(self_) {} + BaseWrap(PyObject* self_, Base const& copy) + : Base(copy), self(self_) {} + int f() { return call_method<int>(self, "f"); } + int default_f() { return Base::f(); } // <<=== ***ADDED*** + PyObject* self; +}; + + + + + + + + + member function and methods + + 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. + + + + + + + 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() +RuntimeError: This class cannot be instantiated from Python + + + +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. +
+
+Deriving a Python Class + +Continuing, we can derive from our base class Base in Python and override +the virtual function in Python. Before we can do that, we have to set up +our class_ wrapper as: + + +class_<Base, BaseWrap, boost::noncopyable>("Base") + ; + + + +Otherwise, we have to suppress the Base class' no_init by adding an +_init_() method to all our derived classes. no_init actually adds +an _init_ method that raises a Python RuntimeError exception. + + +>>> 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<int>(self, "f"); dispatches the call back to Python. + +def f(self): return 42 is finally called. + +
+
+Virtual Functions with Default Implementations + +Recall that in the previous section, we +wrapped a class with a pure virtual function that we then implemented in +C++ or Python classes derived from it. Our base class: + + +struct Base +{ + virtual int f() = 0; +}; + + + +had a pure virtual function f. If, however, its member function f was +not declared as pure virtual: + + +struct Base +{ + virtual int f() { return 0; } +}; + + + +and instead had a default implementation that returns 0, as shown above, +we need to add a forwarding function that calls the Base default virtual +function f implementation: + + +struct BaseWrap : Base +{ + BaseWrap(PyObject* self_) + : self(self_) {} + int f() { return call_method<int>(self, "f"); } + int default_f() { return Base::f(); } // <<=== ***ADDED*** + PyObject* self; +}; + + + +Then, Boost.Python needs to keep track of 1) the dispatch function f and +2) the forwarding function to its default implementation default_f. +There's a special def function for this purpose. Here's how it is +applied to our example above: + + +class_<Base, BaseWrap, BaseWrap, boost::noncopyable>("Base") + .def("f", &Base::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 + + +
+
+Class Operators/Special Functions +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>("FilePos") + .def(self + int()) // __add__ + .def(int() + self) // __radd__ + .def(self - self) // __sub__ + .def(self - int()) // __sub__ + .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". +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_<Rational>() + .def(float_(self)) // __float__ + .def(pow(self, other<Rational>)) // __pow__ + .def(abs(self)) // __abs__ + .def(str(self)) // __str__ + ; + + + +Need we say more? + + + + + + + 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)). + + + + +
+
+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! + +
+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! + +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<args..., + policy2<args..., + policy3<args...> > > + + + +Here is the list of predefined call policies. A complete reference detailing +these can be found here. + + +with_custodian_and_ward + Ties lifetimes of the arguments + +with_custodian_and_ward_postcall + Ties lifetimes of the arguments and results + +return_internal_reference + Ties lifetime of one argument to that of result + +return_value_policy<T> with T one of: + + +reference_existing_object +naive (dangerous) approach + +copy_const_reference +Boost.Python v1 approach + +copy_non_const_reference + + +manage_new_object + Adopt a pointer and hold the instance + + + + + + + + Remember the Zen, Luke: + + +"Explicit is better than implicit" + +"In the face of ambiguity, refuse the temptation to guess" + + + + + +
+
+Overloading + +The following illustrates a scheme for manually wrapping an overloaded +member functions. Of course, the same technique can be applied to wrapping +overloaded non-member functions. + +We have here our C++ class: + + +struct X +{ + bool f(int a) + { + return true; + } + + bool f(int a, double b) + { + return true; + } + + bool f(int a, double b, char c) + { + return true; + } + + int f(int a, int b, int c) + { + return a + b + c; + }; +}; + + + +Class X has 4 overloaded functions. We shall start by introducing some +member function pointer variables: + + +bool (X::*fx1)(int) = &X::f; +bool (X::*fx2)(int, double) = &X::f; +bool (X::*fx3)(int, double, char)= &X::f; +int (X::*fx4)(int, int, int) = &X::f; + + + +With these in hand, we can proceed to define and wrap this for Python: + + +.def("f", fx1) +.def("f", fx2) +.def("f", fx3) +.def("f", fx4) + + +
+
+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, we had to resort to manual +wrapping as outlined in the previous section, or +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_FUNCTION_OVERLOADS +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()); + + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS +Objects here, objects there, objects here there everywhere. More frequently +than anything else, we need to expose member functions of our classes to +Python. Then again, we have the same inconveniences as before when default +arguments or overloads with a common sequence of initial arguments come +into play. Another macro is provided to make this a breeze. + +Like BOOST_PYTHON_FUNCTION_OVERLOADS, +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS may be used to automatically create +the thin wrappers for wrapping member functions. Let's have an example: + + +struct george +{ + void + wack_em(int a, int b = 0, char c = 'x') + { + /*...*/ + } +}; + + + +The macro invocation: + + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(george_overloads, wack_em, 1, 3) + + + +will generate a set of thin wrappers for george's wack_em member function +accepting a minimum of 1 and a maximum of 3 arguments (i.e. the third and +fourth macro argument). The thin wrappers are all enclosed in a class named +george_overloads that can then be used as an argument to def(...): + + +.def("wack_em", &george::wack_em, george_overloads()); + + + +See the overloads reference +for details. +init and optional +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). +
+
+Auto-Overloading + +It was mentioned in passing in the previous section that +BOOST_PYTHON_FUNCTION_OVERLOADS and BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS +can also be used for overloaded functions and member functions with a +common sequence of initial arguments. Here is an example: + + +void foo() +{ + /*...*/ +} + +void foo(bool a) +{ + /*...*/ +} + +void foo(bool a, int b) +{ + /*...*/ +} + +void foo(bool a, int b, char c) +{ + /*...*/ +} + + + +Like in the previous section, we can generate thin wrappers for these +overloaded functions in one-shot: + + +BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3) + + + +Then... + + +.def("foo", foo, foo_overloads()); + + + +Notice though that we have a situation now where we have a minimum of zero +(0) arguments and a maximum of 3 arguments. +Manual Wrapping +It is important to emphasize however that the overloaded functions must +have a common sequence of initial arguments. Otherwise, our scheme above +will not work. If this is not the case, we have to wrap our functions +manually. + +Actually, we can mix and match manual wrapping of overloaded functions and +automatic wrapping through BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS and +its sister, BOOST_PYTHON_FUNCTION_OVERLOADS. Following up on our example +presented in the section on overloading, since the +first 4 overload functins have a common sequence of initial arguments, we +can use BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS to automatically wrap the +first three of the defs and manually wrap just the last. Here's +how we'll do this: + + +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xf_overloads, f, 1, 4) + + + +Create a member function pointers as above for both X::f overloads: + + +bool (X::*fx1)(int, double, char) = &X::f; +int (X::*fx2)(int, int, int) = &X::f; + + + +Then... + + +.def("f", fx1, xf_overloads()); +.def("f", fx2) + + +
+
+ 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++ objects are as close as possible to Python. This +should minimize the learning curve significantly. + + + +
+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, 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. +
+
+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 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 + + +class_<T> as objects +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); + + +
+
+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 the mutable copying problem: + + +dict d = extract<dict>(x.attr("__dict__")); +d['whatever'] = 3;          # modifies x.__dict__ ! + + +
+
+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>("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. + + + + + + + what is a scope? + + 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 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>("X") + .def( ... ) + .def( ... ) + ; + +// Expose X::nested as X.nested +enum_<X::nested>("nested") + .value("red", red) + .value("blue", blue) + ; + + +
+
+Embedding + +By now you should know how to use Boost.Python to call your C++ code from +Python. However, sometimes you may need to do the reverse: call Python code +from the C++-side. This requires you to embed the Python interpreter +into your C++ program. + +Currently, Boost.Python does not directly support everything you'll need +when embedding. Therefore you'll need to use the +Python/C API to fill in +the gaps. However, Boost.Python already makes embedding a lot easier and, +in a future version, it may become unnecessary to touch the Python/C API at +all. So stay tuned... +Building embedded programs +To be able to use embedding in your programs, they have to be linked to +both Boost.Python's and Python's static link library. + +Boost.Python's static link library comes in two variants. Both are located +in Boost's /libs/python/build/bin-stage subdirectory. On Windows, the +variants are called boost_python.lib (for release builds) and +boost_python_debug.lib (for debugging). If you can't find the libraries, +you probably haven't built Boost.Python yet. See and Testing on how to do this. + +Python's static link library can be found in the /libs subdirectory of +your Python directory. On Windows it is called pythonXY.lib where X.Y is +your major Python version number. + +Additionally, Python's /include subdirectory has to be added to your +include path. + +In a Jamfile, all the above boils down to: + projectroot c:\projects\embedded_program ; # location of the program + + # bring in the rules for python + SEARCH on python.jam = $(BOOST_BUILD_PATH) ; + include python.jam ; + + exe embedded_program # name of the executable + : #sources + embedded_program.cpp + : # requirements + <find-library>boost_python <library-path>c:\boost\libs\python + $(PYTHON_PROPERTIES) + <library-path>$(PYTHON_LIB_PATH) + <find-library>$(PYTHON_EMBEDDED_LIBRARY) ; +Getting started +Being able to build is nice, but there is nothing to build yet. Embedding +the Python interpreter into one of your C++ programs requires these 4 +steps: + + +#include <boost/python.hpp> + + + +Call Py_Initialize() to start the interpreter and create the _main_ module. + + + +Call other Python C API routines to use the interpreter. + + + +Call Py_Finalize() to stop the interpreter and release its resources. + + +(Of course, there can be other C++ code between all of these steps.) +
Now that we can embed the interpreter in our programs, lets see how to put it to use...
+
+Using the interpreter + +As you probably already know, objects in Python are reference-counted. +Naturally, the PyObjects of the Python/C API are also reference-counted. +There is a difference however. While the reference-counting is fully +automatic in Python, the Python/C API requires you to do it +by hand. This is +messy and especially hard to get right in the presence of C++ exceptions. +Fortunately Boost.Python provides the handle and +object class templates to automate the process. +Reference-counting handles and objects +There are two ways in which a function in the Python/C API can return a +PyObject*: as a borrowed reference or as a new reference. Which of +these a function uses, is listed in that function's documentation. The two +require slightely different approaches to reference-counting but both can +be 'handled' by Boost.Python. + +For a function returning a borrowed reference we'll have to tell the +handle that the PyObject* is borrowed with the aptly named +borrowed function. Two functions +returning borrowed references are PyImport_AddModule and PyModule_GetDict. +The former returns a reference to an already imported module, the latter +retrieves a module's namespace dictionary. Let's use them to retrieve the +namespace of the _main_ module: + + +object main_module(( + handle<>(borrowed(PyImport_AddModule("__main__"))))); + +object main_namespace = main_module.attr("__dict__"); + + + +For a function returning a new reference we can just create a handle +out of the raw PyObject* without wrapping it in a call to borrowed. One +such function that returns a new reference is PyRun_String which we'll +discuss in the next section. + + + + + + + Handle is a class template, so why haven't we been using any template parameters? + + + +handle has a single template parameter specifying the type of the managed object. This type is PyObject 99% of the time, so the parameter was defaulted to PyObject for convenience. Therefore we can use the shorthand handle<> instead of the longer, but equivalent, handle<PyObject>. + + + + + +Running Python code +To run Python code from C++ there is a family of functions in the API +starting with the PyRun prefix. You can find the full list of these +functions here. They +all work similarly so we will look at only one of them, namely: + + +PyObject* PyRun_String(char *str, int start, PyObject *globals, PyObject *locals) + + + +PyRun_String takes the code to execute as a null-terminated (C-style) +string in its str parameter. The function returns a new reference to a +Python object. Which object is returned depends on the start paramater. + +The start parameter is the start symbol from the Python grammar to use +for interpreting the code. The possible values are: + +Start symbols + +Py_eval_inputfor interpreting isolated expressions + + +Py_file_inputfor interpreting sequences of statements +Py_single_inputfor interpreting a single statement + + + + +When using Py_eval_input, the input string must contain a single expression +and its result is returned. When using Py_file_input, the string can +contain an abitrary number of statements and None is returned. +Py_single_input works in the same way as Py_file_input but only accepts a +single statement. + +Lastly, the globals and locals parameters are Python dictionaries +containing the globals and locals of the context in which to run the code. +For most intents and purposes you can use the namespace dictionary of the +_main_ module for both parameters. + +We have already seen how to get the _main_ module's namespace so let's +run some Python code in it: + + +object main_module(( + handle<>(borrowed(PyImport_AddModule("__main__"))))); + +object main_namespace = main_module.attr("__dict__"); + +handle<> ignored((PyRun_String( + + "hello = file('hello.txt', 'w')\n" + "hello.write('Hello world!')\n" + "hello.close()" + + , Py_file_input + , main_namespace.ptr() + , main_namespace.ptr()) +)); + + + +Because the Python/C API doesn't know anything about objects, we used +the object's ptr member function to retrieve the PyObject*. + +This should create a file called 'hello.txt' in the current directory +containing a phrase that is well-known in programming circles. + + Note that we wrap the return value of PyRun_String in a +(nameless) handle even though we are not interested in it. If we didn't +do this, the the returned object would be kept alive unnecessarily. Unless +you want to be a Dr. Frankenstein, always wrap PyObject*s in handles. +Beyond handles +It's nice that handle manages the reference counting details for us, but +other than that it doesn't do much. Often we'd like to have a more useful +class to manipulate Python objects. But we have already seen such a class +above, and in the previous section: the aptly +named object class and it's derivatives. We've already seen that they +can be constructed from a handle. The following examples should further +illustrate this fact: + + +object main_module(( + handle<>(borrowed(PyImport_AddModule("__main__"))))); + +object main_namespace = main_module.attr("__dict__"); + +handle<> ignored((PyRun_String( + + "result = 5 ** 2" + + , Py_file_input + , main_namespace.ptr() + , main_namespace.ptr()) +)); + +int five_squared = extract<int>(main_namespace["result"]); + + + +Here we create a dictionary object for the _main_ module's namespace. +Then we assign 5 squared to the result variable and read this variable from +the dictionary. Another way to achieve the same result is to let +PyRun_String return the result directly with Py_eval_input: + + +object result((handle<>( +    PyRun_String("5 ** 2" + , Py_eval_input + , main_namespace.ptr() + , main_namespace.ptr())) +)); + +int five_squared = extract<int>(result); + + + + Note that object's member function to return the wrapped +PyObject* is called ptr instead of get. This makes sense if you +take into account the different functions that object and handle +perform. +Exception handling +If an exception occurs in the execution of some Python code, the PyRun_String +function returns a null pointer. Constructing a handle out of this null +pointer throws error_already_set, +so basically, the Python exception is automatically translated into a +C++ exception when using handle: + + +try +{ + object result((handle<>(PyRun_String( + "5/0" + , Py_eval_input + , main_namespace.ptr() + , main_namespace.ptr())) + )); + + // execution will never get here: + int five_divided_by_zero = extract<int>(result); +} +catch(error_already_set) +{ + // handle the exception in some way +} + + + +The error_already_set exception class doesn't carry any information in itself. +To find out more about the Python exception that occurred, you need to use the +exception handling functions +of the Python/C API in your catch-statement. This can be as simple as calling +PyErr_Print() to +print the exception's traceback to the console, or comparing the type of the +exception with those of the +standard exceptions: + + +catch(error_already_set) +{ + if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError)) + { + // handle ZeroDivisionError specially + } + else + { + // print all other errors to stderr + PyErr_Print(); + } +} + + + +(To retrieve even more information from the exception you can use some of the other +exception handling functions listed here.) + +If you'd rather not have handle throw a C++ exception when it is constructed, you +can use the allow_null function in the same +way you'd use borrowed: + + +handle<> result((allow_null(PyRun_String( + "5/0" + , Py_eval_input + , main_namespace.ptr() + , main_namespace.ptr())))); + +if (!result) + // Python exception occurred +else + // everything went okay, it's safe to use the result + + +
+
+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<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: + + +range(start, finish) + +range<Policies,Target>(start, finish) + + +Here, start/finish may be one of: + + +member data pointers + +member function pointers + +adaptable function object (use Target parameter) + + +iterator + + +iterator<T, Policies>() + + +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)); + + +
+
+ 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 const& x) { + PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave..."); +} +BOOST_PYTHON_MODULE(kubrick) { + register_exception_translator< + PodBayDoorException>(translator); + ... + + +
+
+ General Techniques + +Here are presented some useful techniques that you can use while wrapping code with Boost.Python. + +
+Creating Packages + +A Python package is a collection of modules that provide to the user a certain +functionality. If you're not familiar on how to create packages, a good +introduction to them is provided in the +Python Tutorial. + +But we are wrapping C++ code, using Boost.Python. How can we provide a nice +package interface to our users? To better explain some concepts, let's work +with an example. + +We have a C++ library that works with sounds: reading and writing various +formats, applying filters to the sound data, etc. It is named (conveniently) +sounds. Our library already has a neat C++ namespace hierarchy, like so: + + +sounds::core +sounds::io +sounds::filters + + + +We would like to present this same hierarchy to the Python user, allowing him +to write code like this: + + +import sounds.filters +sounds.filters.echo(...) # echo is a C++ function + + + +The first step is to write the wrapping code. We have to export each module +separately with Boost.Python, like this: + + +/* file core.cpp */ +BOOST_PYTHON_MODULE(core) +{ + /* export everything in the sounds::core namespace */ + ... +} + +/* file io.cpp */ +BOOST_PYTHON_MODULE(io) +{ + /* export everything in the sounds::io namespace */ + ... +} + +/* file filters.cpp */ +BOOST_PYTHON_MODULE(filters) +{ + /* export everything in the sounds::filters namespace */ + ... +} + + + +Compiling these files will generate the following Python extensions: +core.pyd, io.pyd and filters.pyd. + + + + + + + The extension .pyd is used for python extension modules, which +are just shared libraries. Using the default for your system, like .so for +Unix and .dll for Windows, works just as well. + + + + + +Now, we create this directory structure for our Python package: + sounds/ + _init_.py + core.pyd + filters.pyd + io.pyd + +The file _init_.py is what tells Python that the directory sounds/ is +actually a Python package. It can be a empty file, but can also perform some +magic, that will be shown later. + +Now our package is ready. All the user has to do is put sounds into his +PYTHONPATH +and fire up the interpreter: + + +>>> import sounds.io +>>> import sounds.filters +>>> sound = sounds.io.open('file.mp3') +>>> new_sound = sounds.filters.echo(sound, 1.0) + + + +Nice heh? + +This is the simplest way to create hierarchies of packages, but it is not very +flexible. What if we want to add a pure Python function to the filters +package, for instance, one that applies 3 filters in a sound object at once? +Sure, you can do this in C++ and export it, but why not do so in Python? You +don't have to recompile the extension modules, plus it will be easier to write +it. + +If we want this flexibility, we will have to complicate our package hierarchy a +little. First, we will have to change the name of the extension modules: + + +/* file core.cpp */ +BOOST_PYTHON_MODULE(_core) +{ + ... + /* export everything in the sounds::core namespace */ +} + + + +Note that we added an underscore to the module name. The filename will have to +be changed to _core.pyd as well, and we do the same to the other extension modules. +Now, we change our package hierarchy like so: + sounds/ + _init_.py + core/ + _init_.py + _core.pyd + filters/ + _init_.py + _filters.pyd + io/ + _init_.py + _io.pyd + +Note that we created a directory for each extension module, and added a +_init_.py to each one. But if we leave it that way, the user will have to +access the functions in the core module with this syntax: + + +>>> import sounds.core._core +>>> sounds.core._core.foo(...) + + + +which is not what we want. But here enters the _init_.py magic: everything +that is brought to the _init_.py namespace can be accessed directly by the +user. So, all we have to do is bring the entire namespace from _core.pyd +to core/_init.py]. So add this line of code to [^sounds/core/init_.py: + + +from _core import * + + + +We do the same for the other packages. Now the user accesses the functions and +classes in the extension modules like before: + + +>>> import sounds.filters +>>> sounds.filters.echo(...) + + + +with the additional benefit that we can easily add pure Python functions to +any module, in a way that the user can't tell the difference between a C++ +function and a Python function. Let's add a pure Python function, +echo_noise, to the filters package. This function applies both the +echo and noise filters in sequence in the given sound object. We +create a file named sounds/filters/echo_noise.py and code our function: + + +import _filters +def echo_noise(sound): + s = _filters.echo(sound) + s = _filters.noise(sound) + return s + + + +Next, we add this line to soundsfilters_init_.py: + + +from echo_noise import echo_noise + + + +And that's it. The user now accesses this function like any other function +from the filters package: + + +>>> import sounds.filters +>>> sounds.filters.echo_noise(...) + + +
+
+Extending Wrapped Objects in Python + +Thanks to Python's flexibility, you can easily add new methods to a class, +even after it was already created: + + +>>> class C(object): pass +>>> +>>> # a regular function +>>> def C_str(self): return 'A C instance!' +>>> +>>> # now we turn it in a member function +>>> C.__str__ = C_str +>>> +>>> c = C() +>>> print c +A C instance! +>>> C_str(c) +A C instance! + + + +Yes, Python rox. + +We can do the same with classes that were wrapped with Boost.Python. Suppose +we have a class point in C++: + + +class point {...}; + +BOOST_PYTHON_MODULE(_geom) +{ + class_<point>("point")...; +} + + + +If we are using the technique from the previous session, +Creating Packages, we can code directly +into geom/_init_.py: + + +from _geom import * + +# a regular function +def point_str(self): + return str((self.x, self.y)) + +# now we turn it into a member function +point.__str__ = point_str + + + +All point instances created from C++ will also have this member function! +This technique has several advantages: + + +Cut down compile times to zero for these additional functions + +Reduce the memory footprint to virtually zero + +Minimize the need to recompile + +Rapid prototyping (you can move the code to C++ if required without changing the interface) + + +You can even add a little syntactic sugar with the use of metaclasses. Let's +create a special metaclass that "injects" methods in other classes. + + +# The one Boost.Python uses for all wrapped classes. +# You can use here any class exported by Boost instead of "point" +BoostPythonMetaclass = point.__class__ + +class injector(object): + class __metaclass__(BoostPythonMetaclass): + def __init__(self, name, bases, dict): + for b in bases: + if type(b) not in (self, type): + for k,v in dict.items(): + setattr(b,k,v) + return type.__init__(self, name, bases, dict) + +# inject some methods in the point foo +class more_point(injector, point): + def __repr__(self): + return 'Point(x=%s, y=%s)' % (self.x, self.y) + def foo(self): + print 'foo!' + + + +Now let's see how it got: + + +>>> print point() +Point(x=10, y=10) +>>> point().foo() +foo! + + + +Another useful idea is to replace constructors with factory functions: + + +_point = point + +def point(x=0, y=0): + return _point(x, y) + + + +In this simple case there is not much gained, but for constructurs with +many overloads and/or arguments this is often a great simplification, again +with virtually zero memory footprint and zero compile-time overhead for +the keyword support. +
+
+Reducing Compiling Time + +If you have ever exported a lot of classes, you know that it takes quite a good +time to compile the Boost.Python wrappers. Plus the memory consumption can +easily become too high. If this is causing you problems, you can split the +class_ definitions in multiple files: + + +/* file point.cpp */ +#include <point.h> +#include <boost/python.hpp> + +void export_point() +{ + class_<point>("point")...; +} + +/* file triangle.cpp */ +#include <triangle.h> +#include <boost/python.hpp> + +void export_triangle() +{ + class_<triangle>("triangle")...; +} + + + +Now you create a file main.cpp, which contains the BOOST_PYTHON_MODULE +macro, and call the various export functions inside it. + + +void export_point(); +void export_triangle(); + +BOOST_PYTHON_MODULE(_geom) +{ + export_point(); + export_triangle(); +} + + + +Compiling and linking together all this files produces the same result as the +usual approach: + + +#include <boost/python.hpp> +#include <point.h> +#include <triangle.h> + +BOOST_PYTHON_MODULE(_geom) +{ + class_<point>("point")...; + class_<triangle>("triangle")...; +} + + + +but the memory is kept under control. + +This method is recommended too if you are developing the C++ library and +exporting it to Python at the same time: changes in a class will only demand +the compilation of a single cpp, instead of the entire wrapper code. + + + + + + + If you're exporting your classes with Pyste, +take a look at the --multiple option, that generates the wrappers in +various files as demonstrated here. + + + + + + + + + + + This method is useful too if you are getting the error message +"fatal error C1204:Compiler limit:internal structure overflow" when compiling +a large source file, as explained in the FAQ. + + + + +
+
+ diff --git a/doc/v2/overloads.html b/doc/v2/overloads.html index c5594a3b..3f524dc6 100644 --- a/doc/v2/overloads.html +++ b/doc/v2/overloads.html @@ -190,7 +190,7 @@ struct X Y inner; }; -BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(X_f_overloads, X::f, 1, 3) +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(f_member_overloads, f, 1, 3) BOOST_PYTHON_MODULE(args_ext) { @@ -205,7 +205,7 @@ BOOST_PYTHON_MODULE(args_ext) class_<X>("X", "This is X's docstring") .def("f1", &X::f, - X_f_overloads( + f_member_overloads( args("x", "y", "z"), "f's docstring" )[return_internal_reference<>()] ) diff --git a/doc/v2/reference.html b/doc/v2/reference.html index 6b77f494..62a7efd9 100644 --- a/doc/v2/reference.html +++ b/doc/v2/reference.html @@ -13,7 +13,7 @@ p.c3 {font-style: italic} h2.c2 {text-align: center} h1.c1 {text-align: center} - + @@ -96,158 +96,217 @@

High Level Components

- -
-
class.hpp/class_fwd.hpp
-
-
-
Classes
-
+
+
class.hpp/class_fwd.hpp
+ +
-
class_
-
bases
+
Classes
+ +
+
+
class_
+ +
bases
+
+
-
-
-
def.hpp
-
-
-
Functions
-
-
-
def
+ +
def.hpp
+ +
+
+
Functions
+ +
+
+
def
+
+
-
-
-
def_visitor.hpp
-
-
-
Classes
-
-
-
enum.hpp
-
-
-
Classes
-
+ +
def_visitor.hpp
+ +
-
enum_
+
Classes
-
-
-
errors.hpp
-
-
-
Classes
-
+ +
enum.hpp
+ +
-
Classes
+ +
+
+
enum_
+
+
+
+
+ +
errors.hpp
+ +
+
+
Classes
+ +
+
+
error_already_set
-
-
-
Functions
-
-
-
+
+ +
Functions
+ +
+
+
handle_exception
-
expect_non_null
-
throw_error_already_set
+
+
-
-
-
exception_translator.hpp
-
-
+
exception_translator.hpp
+ +
+
+
Functions
-
-
-
+
+
register_exception_translator
+
+
-
-
-
init.hpp
-
-
-
Classes
-
+ +
init.hpp
+ +
-
init
-
optional
+
Classes
+ +
+
+
init
+ +
optional
+
+
-
-
-
iterator.hpp
-
-
-
Classes
-
+ +
iterator.hpp
+ +
-
iterator
-
iterators
+
Classes
+ +
+
+
iterator
+ +
iterators
+
+
+ +
Functions
+ +
+
+
range
+
+
-
Functions
-
+ +
module.hpp
+ +
-
range
-
-
-
-
-
module.hpp
-
-
-
Macros
-
-
-
Macros
+ +
+
+
BOOST_PYTHON_MODULE
+
+
+
+
+ +
operators.hpp
+ +
+
+
Classes
+ +
+
+
self_t
+ +
other
+ +
operator_
+
+
+ +
Objects
+ +
+
+
self
+
+
+
+
+ +
scope.hpp
+ +
+
+
Classes
+ +
+
+
scope
+
+
+
+
+ +
wrapper.hpp
+ +
+
+
Classes
+ +
+
+
override
+ +
wrapper
+
+
-
-
operators.hpp
-
-
-
Classes
-
-
-
self_t
-
other
-
operator_
-
-
-
Objects
-
-
-
self
-
-
-
-
-
scope.hpp
-
-
-
Classes
-
-
-
scope
-
-
-
-
-

Object Wrappers

@@ -357,6 +416,7 @@
+
slice.hpp
@@ -978,17 +1038,19 @@

Topics

- -
-
Calling Python Functions and Methods
-
Pickle Support
- Indexing Support
-
+
+
Calling Python Functions and + Methods
+ +
Pickle Support
+ Indexing Support
+

Revised - 19 July, 2003 + 31 October, 2004 +

© Copyright + + + + + Boost.Python - <wrapper.hpp> + + + +
+

+

+ +
+

Boost.Python

+ +

Header <wrapper.hpp>

+
+


+ +

Contents

+ +
+
Introduction
+ +
Classes
+ +
+
+
Class template + override
+ +
+
+
Class + override synopsis
+ +
Class + override observer functions
+
+
+ +
Class template + wrapper
+ +
+
+
Class wrapper + synopsis
+ +
Class + wrapper observer functions
+
+
+
+
+ +
Example(s)
+
+
+ +

Introduction

+ +

To wrap a class T such that its virtual functions can be + "overridden in Python"—so that the corresponding method of a Python + derived class will be called when the virtual function is invoked from + C++—you must create a C++ wrapper class derived from ``T`` that + overrides those virtual functions so that they call into Python. This + header contains classes that can be used to make that job easier.

+ +

Classes

+ +

Class override

+ +

Encapsulates a Python override of a C++ virtual function. An + override object either holds a callable Python object or + None.

+ +

Class override + synopsis

+
+namespace boost
+{
+  class override : object
+  {
+   public:
+      unspecified operator() const;
+      template <class A0>
+      unspecified operator(A0) const;
+      template <class A0, class A1>
+      unspecified operator(A0, A1) const;
+      ...
+      template <class A0, class A1, ...class An>
+      unspecified operator(A0, A1, ...An) const;
+  };
+};
+
+ +

Class override + observer functions

+
+unspecified operator() const;
+template <class A0>
+unspecified operator(A0) const;
+template <class A0, class A1>
+unspecified operator(A0, A1) const;
+...
+template <class A0, class A1, ...class An>
+unspecified operator(A0, A1, ...An) const;
+
+ +
+
Effects: If *this holds a callable Python + object, it is invoked with the specified arguments in the manner + specified here. Otherwise, throws error_already_set + .
+ +
Returns: An object of unspecified type that holds the Python + result of the invocation and, when converted to a C++ type + R, attempts to convert that result object to + R. If that conversion fails, throws error_already_set + .
+
+ +

Class template wrapper

+ +

Deriving your wrapper class from both ``T`` and + ``wrapper<T> makes writing that derived class easier.

+ +

Class template + wrapper synopsis

+
+namespace boost
+{
+  class wrapper
+  {
+   protected:
+      override get_override(char const* name) const;
+  };
+};
+
+ +

Class wrapper + observer functions

+
+override get_override(char const* name) const;
+
+ +
+
Requires: name is a ntbs.
+ +
Returns: If *this is the C++ base class + subobject of a Python derived class instance that overrides the named + function, returns an override object that delegates to the + Python override. Otherwise, returns an override object + that holds None.
+
+ +

Example

+
+#include <boost/python/module.hpp>
+#include <boost/python/class.hpp>
+#include <boost/python/wrapper.hpp>
+#include <boost/python/call.hpp>
+
+using namespace boost::python;
+
+// Class with one pure virtual function
+struct P
+{
+    virtual ~P(){}
+    virtual char const* f() = 0;
+    char const* g() { return "P::g()"; }
+};
+
+struct PCallback : P, wrapper<P>
+{
+    char const* f()
+    {
+#if BOOST_WORKAROUND(BOOST_MSVC, <= 1300) // Workaround for vc6/vc7
+        return call<char const*>(this->get_override("f").ptr());
+#else 
+        return this->get_override("f")();
+#endif 
+    }
+};
+
+// Class with one non-pure virtual function
+struct A
+{
+    virtual ~A(){}
+    virtual char const* f() { return "A::f()"; }
+};
+
+struct ACallback :  A, wrapper<A>
+{
+    char const* f()
+    {
+        if (override f = this->get_override("f"))
+#if BOOST_WORKAROUND(BOOST_MSVC, <= 1300) // Workaround for vc6/vc7
+            return call<char const*>(f.ptr());
+#else 
+            return f();
+#endif 
+
+        return A::f();
+    }
+
+    char const* default_f() { return this->A::f(); }
+};
+
+BOOST_PYTHON_MODULE_INIT(polymorphism)
+{
+    class_<PCallback,boost::noncopyable>("P")
+        .def("f", pure_virtual(&P::f))
+        ;
+
+    class_<ACallback,boost::noncopyable>("A")
+        .def("f", &A::f, &ACallback::default_f)
+        ;
+}
+
+ +

Revised + + 31 October, 2004 + + + +

© Copyright Dave + Abrahams 2004 + diff --git a/include/boost/python/object_protocol.hpp b/include/boost/python/object_protocol.hpp index 1c166237..2011b04a 100755 --- a/include/boost/python/object_protocol.hpp +++ b/include/boost/python/object_protocol.hpp @@ -47,7 +47,7 @@ object getitem(Target const& target, Key const& key) template void setitem(object const& target, Key const& key, Value const& value) { - return setitem(target, object(key), object(value)); + setitem(target, object(key), object(value)); } template diff --git a/test/const_argument.cpp b/test/const_argument.cpp index e40e0ed1..6c5a8945 100644 --- a/test/const_argument.cpp +++ b/test/const_argument.cpp @@ -9,22 +9,22 @@ * compiler's bug. */ #include +#include using namespace boost::python; -bool accept_const_arg_noproto( const object) +BOOST_TT_BROKEN_COMPILER_SPEC( object ) + +#if BOOST_WORKAROUND(BOOST_MSVC, == 1200) +bool accept_const_arg( object ); +#endif + +bool accept_const_arg( const object ) { return true; } -bool accept_const_arg_with_proto( object); -bool accept_const_arg_with_proto( const object) -{ - return true; -} -BOOST_PYTHON_MODULE( const_argument_ext) +BOOST_PYTHON_MODULE( const_argument_ext ) { - def( "accept_const_arg_noproto", accept_const_arg_noproto); - def( "accept_const_arg_with_proto", accept_const_arg_with_proto); + def( "accept_const_arg", accept_const_arg ); } - diff --git a/test/const_argument.py b/test/const_argument.py index 2f1f4a06..15e87e80 100644 --- a/test/const_argument.py +++ b/test/const_argument.py @@ -3,9 +3,7 @@ # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) """ >>> from const_argument_ext import * ->>> accept_const_arg_noproto(1) -1 ->>> accept_const_arg_with_proto(1) +>>> accept_const_arg(1) 1 """