Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu,《C++ 编码标准

使用 Boost.Python 构建混合系统

作者 David Abrahams
联系方式 dave@boost-consulting.com
组织 Boost Consulting
日期 2003-05-14
作者 Ralf W. Grosse-Kunstleve
版权 版权所有 David Abrahams 和 Ralf W. Grosse-Kunstleve 2003 年。保留所有权利

目录

摘要

Boost.Python 是一个开源 C++ 库,它为将 C++ 类和函数绑定到 Python 提供了简洁的类 IDL 接口。它充分利用了 C++ 编译时内省的全部功能和最新开发出的元编程技术,完全使用纯 C++ 实现,无需引入新的语法。Boost.Python 丰富的功能集和高级接口使得从头开始将软件包设计为混合系统成为可能,从而使程序员能够轻松且一致地访问 C++ 高效的编译时多态性和 Python 极其方便的运行时多态性。

简介

Python 和 C++ 在许多方面都截然不同:C++ 通常被编译为机器代码,而 Python 是解释执行的。Python 的动态类型系统通常被认为是其灵活性的基础,而 C++ 中的静态类型是其效率的基石。C++ 具有复杂且困难的编译时元语言,而在 Python 中,几乎所有事情都发生在运行时。

然而,对于许多程序员来说,这些差异恰恰意味着 Python 和 C++ 可以完美地互补。Python 程序中的性能瓶颈可以用 C++ 重写以获得最大速度,而强大的 C++ 库的作者选择 Python 作为中间件语言,因为它具有灵活的系统集成能力。此外,表面上的差异掩盖了一些强大的相似之处

鉴于 Python 丰富的 'C' 互操作性 API,原则上应该可以将 C++ 类型和函数接口暴露给 Python,并具有类似于其 C++ 对等接口的接口。但是,仅 Python 提供的与 C++ 集成的功能相对不足。与 C++ 和 Python 相比,'C' 只有非常基本的抽象功能,并且完全缺少对异常处理的支持。'C' 扩展模块编写者需要手动管理 Python 引用计数,这既令人厌烦又极易出错。传统的扩展模块也往往包含大量样板代码重复,这使得它们难以维护,尤其是在包装不断发展的 API 时。

这些限制导致了各种包装系统的开发。SWIG 可能是 C/C++ 和 Python 集成最流行的软件包。最近的一个发展是 SIP,它专门为 Python 与 Qt 图形用户界面库的接口而设计。SWIG 和 SIP 都引入了自己专门的语言来定制跨语言绑定。这有一定的优势,但是处理三种不同的语言(Python、C/C++ 和接口语言)也带来了实际和精神上的困难。CXX 软件包展示了一个有趣的替代方案。它表明,Python 'C' API 的至少一部分可以被包装并通过更用户友好的 C++ 接口呈现。然而,与 SWIG 和 SIP 不同,CXX 不包括将 C++ 类包装为新的 Python 类型的支持。

Boost.Python 的功能和目标与许多其他系统显着重叠。也就是说,Boost.Python 试图在不引入单独的包装语言的情况下最大化便利性和灵活性。相反,它为用户提供了一个高级 C++ 接口,用于包装 C++ 类和函数,并通过静态元编程在幕后管理大部分复杂性。Boost.Python 还通过提供以下功能超越了早期系统的范围

激发 Boost.Python 开发的关键洞察力是,传统扩展模块中的大部分样板代码都可以使用 C++ 编译时内省来消除。包装的 C++ 函数的每个参数都必须使用取决于参数类型的过程从 Python 对象中提取。类似地,函数的返回类型决定了返回值将如何从 C++ 转换为 Python。当然,参数和返回类型是每个函数类型的一部分,而这正是 Boost.Python 推导出大部分所需信息的来源。

这种方法导致了*用户引导的包装*:尽可能多地直接从要包装的源代码中提取信息,这在纯 C++ 框架内是可能的,并且一些附加信息由用户显式提供。大多数情况下,指导是机械的,几乎不需要真正的干预。由于接口规范是用与要暴露的代码相同的全功能语言编写的,因此当用户确实需要控制时,她拥有前所未有的权力。

Boost.Python 设计目标

Boost.Python 的主要目标是允许用户仅使用 C++ 编译器将 C++ 类和函数暴露给 Python。从广义上讲,用户体验应该是直接从 Python 操作 C++ 对象。

但是,同样重要的是不要*过于*字面地翻译所有接口:必须尊重每种语言的习惯用法。例如,尽管 C++ 和 Python 都有迭代器概念,但它们的表达方式却截然不同。Boost.Python 必须能够弥合接口差距。

必须能够使 Python 用户免受因轻微误用 C++ 接口(例如访问已删除的对象)而导致的崩溃的影响。同样,该库应使 C++ 用户免受低级 Python 'C' API 的影响,从而替换容易出错的 'C' 接口,例如手动引用计数管理和原始PyObject指针,并提供更健壮的替代方案。

对基于组件的开发的支持至关重要,以便在一个扩展模块中暴露的 C++ 类型可以传递给在另一个扩展模块中暴露的函数,而不会丢失关键信息,例如 C++ 继承关系。

最后,所有包装都必须是*非侵入式*的,无需修改甚至查看原始 C++ 源代码。现有的 C++ 库必须能够被仅有权访问头文件和二进制文件的第三方包装。

你好 Boost.Python 世界

现在预览一下 Boost.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 在标准 C++ 中包装此函数,我们需要类似这样的代码

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 <boost/python.hpp>
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 版本更加冗长之外,值得注意的是它无法正确处理的一些事情

库概览

本节概述了库的一些主要功能。除非为了避免混淆是必要的,否则省略了库实现的细节。

暴露类

C++ 类和结构体以类似的简洁接口暴露。给定

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

以下代码将在我们的扩展模块中暴露它

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

尽管这段代码具有一定的 Pythonic 熟悉度,但人们有时会发现语法有点令人困惑,因为它看起来不像他们习惯的大多数 C++ 代码。尽管如此,这只是标准 C++。由于其灵活的语法和运算符重载,C++ 和 Python 非常适合定义特定领域(子)语言 (DSL),而这正是我们在 Boost.Python 中所做的。分解一下

class_<World>("World")

构造一个类型为class_<World>的未命名对象,并将"World"传递给其构造函数。这将在扩展模块中创建一个名为World的新式 Python 类,并将其与 C++ 类型关联World在 Boost.Python 类型转换注册表中。我们也可能写成

class_<World> w("World");

但这会更冗长,因为我们必须命名w再次调用它的def()成员函数

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

原始示例中成员访问的点的位置没有什么特别之处:C++ 允许在标记的任一侧有任意数量的空格,并且将点放在每行的开头允许我们使用统一的语法链接任意数量的连续成员函数调用。允许链接的另一个关键事实是class_<>成员函数都返回对*this.

的引用。因此,该示例等效于

class_<World> 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'

构造函数

由于我们的World类只是一个普通的struct,它具有隐式的无参数(空元)构造函数。Boost.Python 默认暴露空元构造函数,这就是为什么我们能够写成

>>> planet = hello.World()

但是,任何语言中设计良好的类都可能需要构造函数参数才能建立其不变性。与 Python 不同,在 Python 中__init__只是一个特殊命名的函数,在 C++ 中,构造函数不能像普通成员函数那样处理。特别是,我们不能获取它们的地址&World::World是错误的。该库提供了用于指定构造函数的不同接口。给定

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

我们可以如下修改我们的包装代码

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

当然,一个 C++ 类可能还有其他构造函数,我们也可以通过传递更多init<...>的实例来暴露这些构造函数。def():

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

Boost.Python 允许重载包装的函数、成员函数和构造函数,以镜像 C++ 重载。

数据成员和属性

C++ 类中任何公共可访问的数据成员都可以轻松地暴露为只读读写属性,并且可以直接在 Python 中使用

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

这*不会*导致将属性添加到

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

实例的World__dict__,这可以在包装大型数据结构时节省大量内存。实际上,除非从 Python 显式添加属性,否则根本不会创建实例。Boost.Python 将此功能归功于新的 Python 2.2 类型系统,特别是描述符接口和,这可以在包装大型数据结构时节省大量内存。实际上,除非从 Python 显式添加属性,否则根本不会创建实例。property类型。在 C++ 中,公共可访问的数据成员被认为是糟糕设计的标志,因为它们破坏了封装,并且风格指南通常规定使用 “getter” 和 “setter” 函数来代替。然而,在 Python 中,

__getattr____setattr__, ,以及自 2.2 起,意味着属性访问只是程序员可用的另一个封装良好的语法工具。Boost.Python 通过使 Python类型。创建直接可供用户使用。如果类型。msg是私有的,我们仍然可以按如下方式在 Python 中将其暴露为属性上面的示例反映了 Python 2.2+ 中属性的常用用法

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

为用户定义的类型编写算术运算符的能力一直是这两种语言在数值计算方面取得成功的主要因素,而 NumPy 等软件包的成功证明了在扩展模块中暴露运算符的强大功能。Boost.Python 提供了一种简洁的机制来包装运算符重载。下面的示例显示了 Boost 有理数库的包装器的片段

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

运算符重载

魔力是通过使用 “表达式模板” [VELD1995] 的简化应用来实现的,这是一种最初为优化高性能矩阵代数表达式而开发的技术。本质是,运算符被重载以构造*表示*计算的类型,而不是立即执行计算。在矩阵代数中,当可以考虑整个表达式的结构而不是“贪婪地”评估每个操作时,通常可以进行显着的优化。Boost.Python 使用相同的技术来构建基于涉及

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

self的表达式的适当 Python 方法对象.

继承

C++ 继承关系可以通过向 Boost.Python 添加可选的bases<...>参数到class_<...>模板参数列表,如下所示

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

这有两个效果

  1. class_<...>Derived被创建时,对应于Base1Base2的 Python 类型对象在 Boost.Python 的注册表中查找,并用作新的 PythonDerived被创建时,对应于Base1类型对象,因此为 Python 类型暴露的方法自动成为的 Python 类型对象在 Boost.Python 的注册表中查找,并用作新的 PythonDerived的 Python 类型对象在 Boost.Python 的注册表中查找,并用作新的 Python类型的成员。由于注册表是全局的,因此即使 Derived 与其任何一个基类在不同的模块中暴露,这也能正常工作。
  2. 的 Python 类型对象在 Boost.Python 的注册表中查找,并用作新的 PythonDerived的 Python 类型对象在 Boost.Python 的注册表中查找,并用作新的 Python到其基类的 C++ 转换被添加到 Boost.Python 注册表中。因此,期望(指向或引用)任何基类型的对象的包装 C++ 方法可以使用包装 Derived 实例的对象来调用。类T的包装成员函数被视为好像它们具有隐式的第一个参数T&,因此这些转换是必要的,以允许为派生对象调用基类方法。

当然,可以从包装的 C++ 类实例派生新的 Python 类。由于 Boost.Python 使用新式类系统,因此它的工作方式与 Python 内置类型非常相似。但有一个重要的细节不同:内置类型通常在其__new__函数中建立其不变性,以便派生类不需要调用__init____init__

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

在调用其方法之前在基类上。由于 C++ 对象构造是单步操作,因此在参数可用之前,无法构造 C++ 实例数据,在__init____init__

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

函数中。发生这种情况是因为 Boost.Python 无法找到类型为SomeBoostPythonClass的实例数据在D实例中;DD__init__的 __init__ 函数掩盖了基类的构造。可以通过删除 __init__ 函数或让它调用DD__init__SomeBoostPythonClass.__init__(...)SomeBoostPythonClass.__init__(...)显式地来纠正。

虚函数

除非可以从 C++ 以多态方式使用它们,否则从扩展类在 Python 中派生新类型并没有什么意义。换句话说,当*通过来自 C++ 的基类指针/引用*调用时,Python 方法实现应显示为覆盖 C++ 虚函数的实现。由于更改虚函数行为的唯一方法是在派生类中覆盖它,因此用户必须构建一个特殊的派生类来分发多态类的虚函数

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

现在这里有一些 Python 代码演示了

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

关于调度器类需要注意的事项

  • 允许在 Python 中重写的关键元素是call_method调用,它使用与 C++ 函数包装相同的全局类型转换注册表,以将其参数从 C++ 转换为 Python,并将其返回类型从 Python 转换为 C++。
  • 您希望包装的任何构造函数签名都必须使用初始的PyObject*参数复制。
  • 调度器必须存储此参数,以便它可以用于调用call_method
  • Thef_default成员函数。当要暴露的函数不是纯虚函数时,需要成员函数;没有其他方法Base::f可以在类型为BaseWrap的对象上调用,因为它覆盖了f.

更深层次的反射即将到来?

诚然,这个公式重复起来很乏味,尤其是在具有许多多态类的项目中。这之所以必要,反映了 C++ 编译时内省功能的一些限制:无法枚举类的成员并找出哪些是虚函数。至少有一个非常有前途的项目已经启动,旨在编写一个前端,它可以从 C++ 头文件自动生成这些调度器(和其他包装代码)。

Pyste 由 Bruno da Silva de Oliveira 开发。它基于 GCC_XML 构建,GCC_XML 生成 GCC 内部程序表示的 XML 版本。由于 GCC 是一个高度符合标准的 C++ 编译器,这确保了正确处理最复杂的模板代码和完全访问底层类型系统。与 Boost.Python 的理念保持一致,Pyste 接口描述既不会侵入要包装的代码,也不会用一些不熟悉的语言表达:相反,它是一个 100% 纯 Python 脚本。如果 Pyste 成功,它将标志着我们的许多用户将不再直接在 C++ 中包装所有内容。它还将使我们能够选择将一些元编程代码从 C++ 转移到 Python。我们预计很快,不仅我们的用户,而且 Boost.Python 开发人员自己也将对他们自己的代码进行“混合式思考”。

序列化

序列化是将内存中的对象转换为可以存储在磁盘上或通过网络连接发送的形式的过程。序列化对象(通常是纯字符串)可以被检索并转换回原始对象。一个好的序列化系统将自动转换整个对象层次结构。Python 的标准pickle模块就是这样一个系统。它利用该语言强大的运行时内省功能来序列化几乎任意用户定义的对象。通过一些简单且非侵入性的规定,这种强大的机制可以扩展到也适用于包装的 C++ 对象。这是一个例子

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

现在让我们创建一个WorldWorld

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

模块也可以用于更快的处理。Boost.Python 的pickle_suitepickle完全支持标准 Python 文档中定义的协议。就像 Python 中的 __getinitargs__ 函数一样,pickle_suite 的 getinitargs() 负责创建将用于重建 pickle 对象的参数元组。Python pickling 协议的其他元素 __getstate__ 和 __setstate__ 可以通过 C++ getstate 和 setstate 函数可选地提供。C++ 的静态类型系统允许库在编译时确保不使用无意义的函数组合(例如,没有 setstate 的 getstate)。

启用更复杂的 C++ 对象的序列化需要比上面示例中显示的多做一些工作。幸运的是,object接口(参见下一节)极大地帮助保持代码的可管理性。

对象接口

经验丰富的 'C' 语言扩展模块作者将熟悉无处不在的PyObject*PyObject*

、手动引用计数以及需要记住哪些 API 调用返回 “new”(拥有)引用或 “borrowed”(原始)引用。这些约束不仅繁琐,而且是错误的主要来源,尤其是在存在异常的情况下。objectBoost.Python 提供了一个类

objectobject,它可以自动执行引用计数,并提供从任意类型的 C++ 对象到 Python 的转换。这大大减少了潜在扩展模块编写者的学习工作。

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

object创建一个

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

object4Base110从任何其他类型都非常简单。`object` 具有与所有其他类型的模板化交互,并具有自动到 Python 的转换。它发生得如此自然,以至于很容易被忽视

The在上面的例子中,x

double x = extract<double>(o);

Theobjecty在调用索引和乘法运算之前,被转换为 Python 对象。, extract<T>, 类模板可用于将 Python 对象转换为 C++ 类型。如果无法执行任一方向的转换,则在运行时抛出相应的异常。`object` 类型附带一组派生类型,这些类型镜像 Python 内置类型,例如

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

listobjectdict

混合式思考

tuple

等等,尽可能多。这使得从 C++ 方便地操作这些高级类型成为可能

这几乎看起来和工作起来都像常规 Python 代码,但它是纯 C++。当然,我们可以包装接受或返回 `object` 实例的 C++ 函数。

images/python_cpp_mix.png

由于组合编程语言的实际和精神困难,通常在任何开发工作的开始就确定一种语言。对于许多应用程序,性能考虑因素决定了核心算法使用编译语言。不幸的是,由于静态类型系统的复杂性,我们为运行时性能付出的代价通常是开发时间的显着增加。经验表明,编写可维护的 C++ 代码通常比开发可比的 Python 代码花费更长的时间,并且需要*更多*来之不易的工作经验。即使开发人员习惯于专门使用编译语言工作,他们也经常通过某种类型的临时脚本层来增强他们的系统,以造福他们的用户,而从未利用相同的优势。

开发历史

Boost.Python 使我们能够*混合式思考*。Python 可用于快速原型化新应用程序;它的易用性和大量的标准库使我们在通往工作系统的道路上抢占了先机。如有必要,可以使用工作代码来发现限速热点。为了最大限度地提高性能,这些可以用 C++ 重新实现,以及将它们绑定回现有更高级别过程所需的 Boost.Python 绑定。

这个早期版本的目标与我们在本文中描述的许多基本目标相同,最明显的不同之处在于它有一个稍微繁琐的语法,并且缺乏对运算符重载、序列化(pickling)和基于组件的开发的支持。Ullrich Koethe 和 Ralf Grosse-Kunstleve3 很快添加了后三个特性,其他热情的贡献者也加入了进来,贡献了诸如对嵌套模块和静态成员函数的支持等增强功能。

到 2001 年初,开发已经稳定下来,新功能也鲜有增加,然而,一个令人不安的新事实浮出水面:Ralf 已经开始在使用 EDG 前端的编译器预发布版本上测试 Boost.Python,而 Boost.Python 核心中负责处理 Python 和 C++ 类型之间转换的机制编译失败。事实证明,我们一直在利用我们测试过的所有 C++ 编译器实现中一个非常常见的错误。我们知道,随着 C++ 编译器迅速变得更加符合标准,该库将开始在更多平台上失效。不幸的是,由于该机制对于库的运行至关重要,修复这个问题看起来非常困难。

幸运的是,那年晚些时候,劳伦斯伯克利国家实验室和后来的劳伦斯利弗莫尔国家实验室与 Boost Consulting 签订了合同,以获得对 Boost.Python 的支持和开发,这为解决根本问题并确保该库的未来提供了新的机会。一项重新设计工作从底层类型转换架构开始,构建了符合标准且支持基于组件的开发(与版本 1 形成对比,在版本 1 中,转换必须在模块边界之间显式导入和导出)。对 Python 和 C++ 对象之间关系的新分析也已完成,从而为 C++ 左值和右值提供了更直观的处理。

Python 2.2 中强大的新类型系统的出现,使得是否保持与 Python 1.5.2 的兼容性变得容易选择:仅仅是抛弃大量用于模拟经典 Python 类的复杂代码的机会就太好了,不容错过。此外,Python 迭代器和描述符为表示类似的 C++ 构造提供了至关重要且优雅的工具。通用化的发展object接口使我们能够进一步保护 C++ 程序员免受 Python 'C' API 的危险和语法负担。在此期间,还添加了许多其他功能,包括 C++ 异常转换、改进的对重载函数的支持,以及最重要的,用于处理指针和引用的 CallPolicies。

2002 年 10 月,Boost.Python 的版本 2 发布。此后的开发集中在改进对 C++ 运行时多态和智能指针的支持上。Peter Dimov 精巧的boost::shared_ptr设计尤其使我们能够为混合开发者提供一致的接口,以便在跨语言边界来回移动对象而不会丢失信息。起初,我们担心 Boost.Python v2 实现的复杂性和复杂性可能会使贡献者望而却步,但 Pyste 的出现以及其他几项重要的功能贡献已经消除了这些担忧。Python C++-sig 上的日常问题和大量期望的改进表明该库正在被使用。对我们来说,未来一片光明。

结论

Boost.Python 实现了两种丰富且互补的语言环境之间的无缝互操作性。因为它利用模板元编程来内省类型和函数,所以用户永远不必学习第三种语法:接口定义是用简洁且可维护的 C++ 编写的。此外,包装系统不必解析 C++ 头文件或表示类型系统:编译器为我们完成了这项工作。

计算密集型任务发挥了 C++ 的优势,并且通常不可能在纯 Python 中高效地实现,而像序列化这样在 Python 中微不足道的工作在纯 C++ 中可能非常困难。考虑到从头开始构建混合软件系统的优势,我们可以以新的信心和力量来对待设计。

引用

[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

脚注

[1]回想起来,从一开始就“混合思考”可能对 NLP 系统更好:纯 Python 原型定义的自然组件边界被证明不适合从 C++ 核心中获得期望的性能和内存占用,这最终导致在将核心移动到 C++ 时,Python 方面的一些重新设计开销。
[2]除非这是最终使用的唯一方式,否则我们对通过 Python 接口驱动所有 C++ 测试也有一些保留意见。跨越具有如此不同对象模型的语言边界的任何转换都不可避免地会掩盖错误。
[3]这些特性在 Boost.Python 的 v1 版本中表达方式非常不同