From 0ccded7df34a8f0953ca45cf9b957ab8ba73fe74 Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Mon, 1 Nov 2004 21:24:48 +0000 Subject: [PATCH] merged from trunk [SVN r26068] --- doc/PyConDC_2003/bpl.html | 1135 +------------------------------------ doc/PyConDC_2003/bpl.pdf | Bin 96643 -> 7316 bytes doc/PyConDC_2003/bpl.txt | 948 +------------------------------ 3 files changed, 13 insertions(+), 2070 deletions(-) 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
- -
-

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.

-
-
-

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:

-
    -
  • 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.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 486c1d8bbb4ecaac138f405606d5acc1c7967690..09827aff02ec21d5897dbbaf8efc7658be268fda 100755 GIT binary patch literal 7316 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 S$_O;>k35@XXlMqXPXYjdh$RaE literal 96643 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 z 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 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.