下面介绍一些有用的技术,您可以在使用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.pyd
和io.pyd
。
![]() | 注意 |
---|---|
扩展名 |
现在,我们为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
对象中按顺序应用echo
和noise
滤波器。 我们创建一个名为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中所述。 |