使用Boost.Python构建混合系统

使用Boost.Python构建混合系统

Building Hybrid Systems with Boost.Python

Author: David Abrahams
Contact: [email protected]
Organization: Boost Consulting
Date: 2003-03-19
Author: Ralf W. Grosse-Kunstleve
Translation: 王志勇( [email protected])
Copyright: Copyright David Abrahams and Ralf W. Grosse-Kunstleve 2003. All rights reserved

Table of Contents

  • 概要
  • 介绍
  • Boost.Python 的设计目标
  • Hello Boost.Python World
  • Library Overview
    • 导出 Classes
      • 构造函数(Constructors)
      • 数据成员和属性(Data Members and Properties)
      • 操作符重载(Operator Overloading)
      • 继承(Inheritance)
      • 虚函数(Virtual Functions)
      • Deeper Reflection on the Horizon?
    • 序列化(Serialization)
    • Object 接口(Object interface)
  • Thinking hybrid
  • 开发历史
  • 总结
  • 引用
  • 脚注

概要

Boost.Python是一个开源C++库,她提供了一个简明的 IDL 式的接口用于绑定C++类和函数到Python。 得益于C++编译期的内部处理(译注:原文是introspection,我不知道怎么翻译合适)和最近开发的元编程(metaprogramming)技术,成就了Boost.Python不需引入一种新的语法而只用纯C++的实现。Boost.Python丰富的特性集合以及她的高 阶接口使得工程师像混合系统(译注:hybrid system,我听说过油/电混合动力系统)那样做打包的事情成为可能,并且程序员让在应用C++高效的编译期多态性以及Python非常方便的运行期多态性的时候获得易用性和一致性;

介绍

Python和C++在很多方面相当的不同:C++一般编译为机器码,Python是解释处理的。Python的动态类型系统(dynamic type system )作为语言具有灵活性的基础常常被提及,然而C++的静态类型系统是她效率的基石。C++拥有一种复杂和难以理解的编译期元语言(compile-time meta-language),然而在Python里头几乎所有事情都发生在运行期。

但是对于很多程序员来说,这些很大的不同也意味着Python和C++可以完美的互补。Python程序内性能瓶颈的部分可以用C++重写以带来最高的运行速度, 并且强大的C++库的作者们可以选择Python作为中间件语言,利用她灵活的系统集成能力。此外, 表面上的不同也掩盖了一些非常相似之处:

  • C语言家族的控制结构(if, while, for...)
  • 支持面向对象、函数化编程以及普通语言(these are both multi-paradigm programming languages.)
  • 全面的操作符重载机制,重视为了代码可读性和表达性而提高语法可变性
  • 高层概念,例如集合和迭代器(collections and iterators)
  • 上层封装机制(C++:namespace,Python:modules)以便支持重用库的设计
  • 异常捕获机制用于错误情况管理
  • C++ idioms in common use, such as handle/body classes and reference-counted smart pointers mirror Python reference semantics

提供给Python丰富的‘C’互操作API,应当尊许一个法则:导出C++的类型和函数接口给Python的时候,尽量用和C++相似的接口。然而,Python自己提供的C++集成接口 是非常贫乏的。比较C++和 Python ,‘C’只有非常基本的抽象能力,而且不支持异常处理机制。‘C’扩展模块的作者被要求手动管理Python的引用计数,那 是非常麻烦而且容易出错的事情。传统的扩展模块往往包含大量的重复的‘样板代码’,使得代码难以维护,特别是当你封装一个发展中的API(译注:指还未完善的API)的时候。

这些限制导致出现了大量的Python封装系统。 SWIG 大概是最流行的一种用于集成 C/C++ 和 Python 的系统。最近出现的一种是 SIP,特别设计用于集成 Python 和 Qt  图形用户接口。SWIG 和 SIP 都引入了他们自己的专门语言用于定制语言间绑定。这么做有一定的好处,但是你不得不去处理三种语言( Python 、C/C++以及引入的接口语言),所以也带来了实际困难。CXX 则演示了另外一种有趣的 选择。她证明了至少部分的 Python 的 'C' API 可以被更友好的 C++接口封装。然而,不像 SWIG 和 SIP 那样,CXX 不包含对封装 C++类和 Python 新类型(new Python types)的支持。

Boost.Python 的特性和目标与很多其它的封装系统是一样的。 就是说 Boost.Python 企图提供最大化的易用性和灵活性,但是并不引入一种独立的封装语言。取而代之的是,她提供高级的C++接口给用户用于封装C++类和函数,并且通过静态元程序(static metaprogramming)管理大量内部的复杂性。Boost.Python 超越了早期 封装系统提供的特性,包括:

  • 支持能够被Python重载的C++虚函数。
  • 对于低阶的C++指针和引用(low-level C++ pointers and references),提供全面的生命期管理机制。
  • 支持把扩展功能封装为package,通过中心的注册机制作语言间类型转换。
  • 通过一种安全和易用的方法,用于引入Python强大的序列化引擎(pickle)。
  • 与C++对lvalues、rvalues的处理机制一致,所以可以对C++以及Python的类型系统深入的理解(译注:明白了一个就明白了另外一个)。

开发Boost.Python最主要的目的是,通过使用C++的编译期内部处理(原文是:introspection),大量的传统扩展模块样板代码可以被排除。每个封装的C++参数必须从一个Python对象中取得,根据参数的类型使用不同的过程(procedure)进行处理。同样地,函数的返回类型决定了返回值怎样从C++到Python进行转换。当然,参数和返回值的类型是每个函数类型的一部分,这就是Boost.Python得出大部分所需信息的源头。

这种方法引入了用户指导封装:在纯C++的框架范围内,使得直接导出(到Python)的信息和被封装的源代码一样多成为了可能,一些额外的信息由用户显式 地提供。通常这种‘指导’是机械化的几乎不需要用户实际的干涉。因为接口的规范是用和表述代码同样的全特性语言描述的,用户拥有了空前的能力,当他想进行控制的时候。

Boost.Python 的设计目标

Boost.Python的主要设计目标是让用户可以只通过C++编译器,就能在Python内使用C++类和函数。简单说,用户感觉就是直接从Python里面操作C++对象。

然而,不逐个转换所有接口也是很重要的:每种语言的习惯必须被尊重。例如,尽管C++和Python都有迭代器(iterator)的概念,他们的表达方式却很不同。Boost.Python不得不具有能把他们结合在一起的能力。

把Python用户从C++接口里的琐碎错误中隔离开必须是可能的,例如访问已经被删除了的对象。同样的,必须使C++用户从低阶Python 'C' API 隔离开来,用更好的选择去替换掉像手工进行引用计数管理新的 PyObject 指针管理这些事情,他们都是容易导致错误的 'C' 接口。

支持基于组件的开发也是至关重要的,所以,一个扩展模块中的C++导出类型可以被传递到另外一个模块导出的函数内应用,而且不丢失任何重要信息,例如C++的继承关系。

最后,所有的封装必须是非干扰性的,不能修改甚至只是‘查看’原始的C++代码。现存的C++库对于只有header文件和二进制文件的第三方来说,已经是可封装的了。

Hello Boost.Python World

现在先来个Boost.Python的预览,看看她是如何改进Python的原有机制的。下面是我们可能要用于Python的函数:

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

使用 Python 的 'C'  API 封装这个函数,我们需要这样做:

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

现在,这是使用 Boost.Python 的封装代码:

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

这里演示了如何使用:

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

实际上'C' API的版本更冗长,it's worth noting a few things that it doesn't handle correctly:

  • 原始函数接受一个无符号整数参数,然而Python 'C' API只提供了提取有符号整数的方式给我们。如果我们传递一个负数给hello.greetBoost.Python 版本会 抛出一个 Python 异常,但是另外一个版本将会执行下去,不管C++的实现中在什么时候转换负整数到无符号数(通常封装成很大的数),然后传递不正确的转换过的参数到被封装的函数。
  • 这带给了我们第二个问题:如果C++的greet()函数被一个比2大的参数调用,它会抛出一个异常。典型地,如果一个C++异常通过'C'编译器生成的代码 的边界进行传递,会引起崩溃(crash)。像你在第一个版本中看到的那样,那里没有阻止它(crash)发生的C++机制。Boost.Python封装的函数自动包含了一个异常处理层,它能通过转换未捕获的C++异常到对应的Python异常以保护Python用户。
  • 有点更微妙的限制是:使用Python 'C' API进行参数转换的示例只能用一种方式取得整数x。PyArg_ParseTuple 无法转换 Python 的 long 对象(任意精度整数)它正好适合一个unsigned int 而不是signed long, 也不能通过一个封装的带有用户显式定义的operator unsigned int() 的C++类来转换 。Boost.Python的动态类型转换注册(dynamic type conversion registry)允许用户添加任意的转换方法。

Library Overview

这一部分描述了库的主要特性。为了避免混乱,库的实现细节被省略了。

导出Classes

C++类和结构是用同样简洁的接口导出的:

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

下面的代码会导出它到我们的扩展模块:

#include 
BOOST_PYTHON_MODULE(hello)
{
    class_("World")
        .def("greet", &World::greet)
        .def("set", &World::set)
    ;
}

尽管这些代码有某种pythonic familiarity(译注:或许是Python风格的意思),人们有时还是发现这种语法有点令人迷惑,因为它看上去不像他们过去使用的C++代码。其实,这就是标准C++的实现。由于他们的灵活的语法和操作符重载,C++和Python在定义domain-specific (sub)languages (DSLs)上是非常出色的,那就是我们在Boost.Python里面做的。把它拆开看:

class_("World")

构造一个未命名的class_类型的对象并且传递"World" 到它的构造函数。这就在扩展模块里面创造了一个新的Python class叫作World ,并且把它在Boost.Python类型转换注册(type conversion registry)里头和C++类型World 关联起来了。我们可能也会写下:

class_ w("World");

但是那会显得更冗长,因为我们不得不再次通过w 去调用def() 成员函数:

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

在原来示例中的成员访问形式——‘点’(dot)没什么特别的:C++允许在一个表达式的任何一边写下任何数量的空白,把‘点’放在每行代码的开始允许我们连续的调用成员函数,因为我们喜欢统一形式的语法。另外一个关键的 、允许实现链式语法的事实是class_<> 成员函数都返回一个到 *this 的引用(reference)。

所以这个示例等于:

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

这种形式偶尔是有用的,以便用这种方式分解 Boost.Python 的类封装,但是文章剩下的部分将会使用简洁的语法。

这里是封装类的使用:

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

构造函数(Constructors)

由于我们的World 类只是一个struct, 它有一个隐式的无参数(空的)的构造函数。Boost.Python缺省的会公开这个构造函数(给Python),所以我们可以这样写:

>>> planet = hello.World()

然而,在任何语言里面的良好设计的类都会需要构造函数参数——用于建立他们的不变量(invariants)。不像Python,她的__init__ 只是一个特定命名的方法,在C++里构造函数不能像普通成员函数那样被掌控。特别地,我们不能取他们的地址:&World::World 是一个错误。(Boost.Python)库提供了一种不同的接口以指定构造函数,像这样:

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

我们可以像下面这样更改我们的封装代码:

class_("World", init())
    ...

当然,一个C++类可以有额外的构造函数,而且我们可以通过更多的def() init<...> 实例把它们导出:

class_("World", init())
    .def(init())
    ...

Boost.Python允许封装的函数、成员函数和构造函数被重载以反映他们在C++中的重载关系(to be overloaded to mirror C++ overloading).

数据成员和属性(Data Members and Properties)

任何public的C++数据成员都可以容易地导出成readonly 或 readwrite 属性:

class_("World", init())
    .def_readonly("msg", &World::msg)
    ...

也可以直接在Python内部使用:

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

这不会造成在World 实例内增加一个 __dict__的结果,这么做可以在封装大型数据结构时节省内存。实际上,根本没有__dict__ 实例被创造除非显式地在 Python 里面给它增加属性。Boost.Python 把这种能力感激于Python 2.2 的类型系统,特别是描述符接口(descriptor interface)和 property 类型。

在C++里,具有public属性的数据成员被认为是一种糟糕的设计,因为它们破坏了封装性,并且风格指导通常指示使用"getter"和"setter"函数作为代替。在Python里, 对应__getattr__ __setattr__,从2.2开始property意味着属性访问是一个程序员可用的,封装性更好的语法工具。Boost.Python通过使Python的property 直接被创建并且对用户可用,弥合了这种语言习惯上的缝隙。即使msg 是private的,我们还是可以把它作为属性(attribute)给Python使用,通过:

class_("World", init())
    .add_property("msg", &World::greet, &World::set)
    ...

上面的示例和Python 2.2+内使用properties的用法是一样的:

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

具有编写针对用户定义数据类型的算术操作符(arithmetic operators)的能力已经成为一个数学计算语言主要的成功因素,像NumPy 这样成功的包证明了在扩展模块里导出操作符的威力。Boost.Python提供了一种很简单的机制以实现封装操作符重载。下面的例子是一个Boost有理数库封装内部的代码片断:

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

这里的魔法是应用一种简化的表达式模板("expression templates") [VELD1995],一种原来用于开发high-performance matrix algebra expressions的技术。它的本质是利用重载的操作符构造一个类型以表示计算,而不是立即进行计算工作。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 使用同样的技术构建一个适当的Python方法对象,这基于在表达式内包含self

继承(Inheritance)

C++继承关系可以用Boost.Python描述,通过添加一个可选的bases<...> 参数到class_<...> 模板参数列表,像下面这样:

class_ >("Derived")
     ...

这有两种作用:

  1. 当类class_<...> 被创建的时候,Boost.Python在注册项(registry)里面查找Base1 和 Base2 对应的Python类型对象,并且把他们作为新的Python Derived 类型对象的基类,所以Base1 和 Base2 类型的方法自动成为Derived 类型的成员。因为注册项(registry)是全局的,所以即使Derived 是(和Base1/Base2)在不同的模块里头也有作用。
  2. Derived 到它的基类的转换也被添加的Boost.Python的注册项里。因而可以在每个包含了Derived 实例的对象内部,调用 封装的C++方法所需要(指向或引用到)的每个基类类型。class T 的被封装的成员函数被看作有一个隐式的第一个参数T&,,所以那些用以允许基类方法被派生类调用的转换是必要的。

当然从封装的C++类实例派生出新的Python对象也是可能的。因为Boost.Python使用new-style class system,他们和Python内建类型的工作方式很像。有一个重大的细节上的不同之处:内建类型通常通过__new__ 函数建立他们自己的不变量(invariants),所以派生类在使用(基类的)方法前不需要调用基类的__init__ :

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

因为C++的对象构造是一个单步操作,C++不能构造(对象)实例数据直到参数可用。在__init__ 函数里:

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

它会出错,因为在D实例内部 Boost.Python 找不到 SomeBoostPythonClass 的实例数据;D 的 __init__ 函数遮蔽了基类的构造。移除D 的 __init__ 函数或者在 SomeBoostPythonClass.__init__(...)内部显式的调用它都是正确的。

虚函数(irtual Functions)

在Python里面, 从扩展类型派生出新的类型不是有趣的事情,除非他们能从C++被多态地使用。换句话说,Python方法的实现应当看上去是重载C++虚函数的实现,当从C++通过基类的指针/引用调用(这个虚函数)的时候。因为唯一的改变一个虚函数行为的办法是在派生类内重载它,用户必须创建一个特殊的派生类以转发(dispatch)实现这种虚函数的多态性:

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

现在,这里是一些Python演示代码:

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

关于转发类需要注意:

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

序列化的的含义是把内存中的对象转换成能够存储到磁盘或者通过网络连接发送的形式。序列化后生成的对象(大多数时候是一种字符串)能被重新转化到原始对象。一个好的序列化系统会自动的转化整个对象体系。Python的pickle 模块就是这样一个系统。它得益于这种语言强大的运行期内部处理(译注:instrospection)能力,几乎能序列化任意用户定义的对象。只需要通过加入一些简单 的、非打扰式的处理,这种强大机制就能够扩展到为封装的C++对象工作。下面是一个例子:

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

现在,我们创建一个World 对象并且把它放在磁盘上休息:

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

然后,可能是在不同的计算机上不同的操作系统的不同的一个脚本上,我们这样用:

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

当然,使用 cPickle (译注:cPickle是更高效率的一种pickle实现)模块可以更快速的处理。

Boost.Python 的 pickle_suite 完全支持标准Python文档定义的pickle 协议。像Python的__getinitargs__ 函数那样,pickle_suite 的 getinitargs() 函数负责创建argument tuple用以重建pickle过的对象。Python pickling 协议的其他元素, __getstate__ and __setstate__ 可以通过C++ getstate和setstate函数选择提供。C++的静态类型系统允许库确保在编译期避免无意义的函数合并(例如:getstate 却没有 setstate)被应用。

使更复杂的C++对象能够被序列化要比上面的示例需要更多的工作。幸运的是object 接口(查看下一部分)在代码可管理性上非常地有帮助。

Object 接口(Object interface)

有经验的C语言扩展模块接口作者应该很熟悉PyObject*,手动引用计数(reference-counting),而且需要记住哪个API返回"新的" (拥有的) 引用或者 "借来的" (raw) 引用。这些限制不仅仅是很麻烦,重要的这也是主要的错误源,特别是在异常的表示(presence of exceptions)上。

Boost.Python提供了一个object 类,能够自动进行引用计数并且提供从任意C++对象到Python对象的转换。这对于想成为扩展模块作者的人来说,极大的减少了学习困难。

从任何其他类型创建一个object 是非常简单的:

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

object 可以和所有其他数据类型进行模板化的交互(templated interactions ),并且能够自动完成到python的转换。这些都进行得非常自然以至于它很容易被忽略掉:

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

在上面的示例里,4 和 10 在进行索引操作和乘法操作调用(indexing and multiplication operations)前,被转化为Python对象。

extract class 模板能够用来转换Python对象到C++类型:

double x = extract(o);

如果任何一侧的转换不能进行,一个适当的exception将会在运行期被抛出。

object 类型与Python内建类型的‘副本’如:listdicttuple等等成为一套。这使得从C++转换到这些高阶类型变得方便操作:

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

它的工作方式和看上去的样子几乎和一般的Python代码一样,但是它是纯C++的。当然我们可以封装接受或者返回object 实例的C++函数。

混合地思考(Thinking hybrid)

由于在组合不同的编程语言时实际上的和心理上的困难,通常在开始先确定单独的一种语言。对于任何应用程序来说,性能上的考虑决定了在核心算法上使用编译语言(compiled language)。不幸的是,由于静态类型系统的复杂性,我们为运行期性能所付出的代价通常在开发期极大的增加。经验显示:相对于开发同等的Python代码来说,写出可维护的C++代码通常需要更长时间和更多努力工作得来的经验。即使当开发者们用编译语言(compiled language)感觉很舒服的时候,他们也常常为他们的系统增加某种类型的脚本层,因为他们的用户可以获得同样的使用脚本语言的好处。

Boost.Python 让我们可以混合地思考。Python可以作为一些应用程序的快速原型;她的易用性和巨大的标准库给了我们到一个工作中的系统的一个开始。如果有必要,这些工作代码可以用来揭示热点比率(译注:意思是发现哪些代码运行最频繁或者占用时间/资源最多)。为了最大化提高性能,那些(热点)可以被C++重新实现,然后用Boost.Python把他们绑定到现有的高阶过程(higher-level procedure)中。

当然,自上而下的过程不是那么吸引人,如果从开始就有许多代码不得不改成用C++实现。幸运的是Boost.Python允许我们应用自下而上的过程。我们曾经应用这种过程非常成功地开发了一个科学软件的工具箱。这个工具箱的开始的时候主要是一个带有Boost.Python绑定的C++类,过了一段时间,成长的部分主要集中在C++的部分。然而由于这个工具箱越来越复杂,越来越多的新特性可以在Python内被实现。


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 wasSWIG, 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 ingeniousboost::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

你可能感兴趣的:(系统分析与设计,技术文章,C++,python,overloading,serialization,class,object,string)