Boost.Python教程:通用技术

26 篇文章 0 订阅

下面介绍一些有用的技术,您可以在使用Boost.Python包装代码时使用这些技术。

创建包

Python包是一组模块,为用户提供某种功能。 如果您不熟悉如何创建包, Python教程中提供了对它们的一个很好的介绍。

但是我们使用Boost.Python包装C ++代码。 我们如何为用户提供一个漂亮的包界面? 为了更好地解释一些概念,让我们使用一个例子。

我们有一个有不同含义的C ++库:读取和写入各种格式,对声音数据应用滤波器等。它被命名(方便) sounds 。 我们的库已经有了一个简洁的C ++命名空间层次结构,如下所示:

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

我们想向Python用户提供相同的层次结构,允许他编写如下代码:

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

第一步是编写包装代码。 我们必须使用Boost.Python单独导出每个模块,如下所示:

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

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

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

编译这些文件将生成以下Python扩展: core.pyd , io.pydio.pyd 。

[注意]注意

扩展名.pyd用于python扩展模块,它们只是共享库。 使用系统的默认设置,例如Unix的.so和Windows的.dll ,也可以。

现在,我们为Python包创建这个目录结构:

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

文件__init__.py告诉Python该目录sounds/实际上是一个Python包。 它可以是一个空文件,但也可以执行一些魔术,稍后将会显示。

现在我们的包准备好了。 用户所要做的就是将sounds放入他的PYTHONPATH并启动解释器:

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

很不错吧?

这是创建包层次结构的最简单方法,但它不是很灵活。 如果我们想要将 Python函数添加到过滤器包中,例如,一次在声音对象中应用3个过滤器,该怎么办? 当然,您可以在C ++中执行此操作并将其导出,但为什么不在Python中执行此操作? 您不必重新编译扩展模块,而且编写它会更容易。

如果我们想要这种灵活性,我们将不得不使我们的包层次结构复杂化。 首先,我们将不得不更改扩展模块的名称:

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

请注意,我们在模块名称中添加了下划线。 文件名也必须更改为_core.pyd ,我们对其他扩展模块执行相同的操作。 现在,我们改变我们的包层次结构如下:

sounds/
    __init__.py
    core/
        __init__.py
        core.pyd
    filters/
        \_init__.py
        filters.pyd
    io/
        \_init__.py
        _io.pyd

请注意,我们为每个扩展模块创建了一个目录,并为每个扩展模块添加了__init__.py。 但是如果我们这样离开,用户将不得不使用以下语法访问核心模块中的函数:

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

这不是我们想要的。 但是这里输入__init__.py魔术:用户可以直接访问带到__init__.py命名空间的所有内容。 因此,我们所要做的就是将整个命名空间从_core.pyd带到core/__init__.py 。 所以将这行代码添加到sounds/core/__init__.py :

from _core import *

我们对其他包也这样做。 现在,用户像以前一样访问扩展模块中的函数和类:

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

另外一个好处是我们可以轻松地将纯Python函数添加到任何模块中,这样用户就无法区分C ++函数和Python函数。 让我们将一个 Python函数echo_noise添加到filters包中。 此函数在给定的sound对象中按顺序应用echonoise滤波器。 我们创建一个名为sounds/filters/echo_noise.py的文件并编写我们的函数:

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

接下来,我们将此行添加到sounds/filters/__init__.py :

from echo_noise import echo_noise

就是这样。 用户现在可以像filters包中的任何其他功能一样访问此功能:

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

在Python中扩展包装对象

由于Python的灵活性,您可以轻松地向类添加新方法,即使它已经创建了:

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

是的,Python rox。 

我们可以对使用Boost.Python包装的类执行相同的操作。 假设我们在C ++中有一个类point :

class point {...};

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

如果我们使用上一个会话创建包中的技术 ,我们可以直接编码到geom/__init__.py :

from _geom import *

# a regular function
def point_str(self):
    return str((self.x, self.y))

# now we turn it into a member function
point.__str__ = point_str

从C ++创建的所有点实例也将具有此成员函数! 这种技术有几个优点:

  • 对于这些附加功能,将编译时间减少到零
  • 将内存占用减少到几乎为零
  • 最大限度地减少重新编译的需要
  • 快速原型设计(如果需要,您可以将代码移动到C ++而无需更改接口)

您甚至可以使用元类添加一点语法糖。 让我们创建一个特殊的元类,在其他类中“注入”方法。

# The one Boost.Python uses for all wrapped classes.
# You can use here any class exported by Boost instead of "point"
BoostPythonMetaclass = point.__class__

class injector(object):
    class __metaclass__(BoostPythonMetaclass):
        def __init__(self, name, bases, dict):
            for b in bases:
                if type(b) not in (self, type):
                    for k,v in dict.items():
                        setattr(b,k,v)
            return type.__init__(self, name, bases, dict)

# inject some methods in the point foo
class more_point(injector, point):
    def __repr__(self):
        return 'Point(x=%s, y=%s)' % (self.x, self.y)
    def foo(self):
        print 'foo!'

现在让我们看看它是如何得到的:

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

另一个有用的想法是用工厂函数替换构造函数:

_point = point

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

在这个简单的情况下,没有太多的收获,但对于具有许多重载和/或参数的构造函数,这通常是一个很大的简化,同样几乎没有内存占用和关键字支持的零编译时开销。

缩短编译时间

如果您曾经导出过很多类,那么您知道编译Boost.Python包装器需要相当长的时间。 此外,内存消耗很容易变得太高。 如果这导致您出现问题,可以将class_ definitions拆分为多个文件:

/* file point.cpp */
#include <point.h>
#include <boost/python.hpp>

void export_point()
{
    class_<point>("point")...;
}

/* file triangle.cpp */
#include <triangle.h>
#include <boost/python.hpp>

void export_triangle()
{
    class_<triangle>("triangle")...;
}

现在,您创建一个main.cpp文件,其中包含BOOST_PYTHON_MODULE宏,并调用其中的各种导出函数。

void export_point();
void export_triangle();

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

编译并链接所有这些文件会产生与通常方法相同的结果:

#include <boost/python.hpp>
#include <point.h>
#include <triangle.h>

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

但记忆得到了控制。

如果您正在开发C ++库并同时将其导出到Python,也建议使用此方法:类中的更改只需要编译单个cpp,而不是整个包装器代码。

[注意]注意

如果在编译大型源文件时收到错误消息“致命错误C1204:编译器限制:内部结构溢出” ,此方法也很有用,如FAQ中所述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格拉斯范朋克

播种花生牛奶自留田

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值