使用 C++ 和 Python 访问数组
Posted
技术标签:
【中文标题】使用 C++ 和 Python 访问数组【英文标题】:Access an Array with both C++ and Python 【发布时间】:2014-05-12 08:31:36 【问题描述】:我正在模拟一个由大约 100,000 颗恒星组成的迷你星系。我想在 Python 中做的视觉表示,在 C++ 中的大型计算。使用 ctypes,我可以从 Python 调用 C++ 函数。
我基本上想要的是一个存在于 RAM 中的数组,它可以被 python 和 C++ 访问。然后在 python 中调用函数 update() 时,C++ 会更新数组。重要的是 C++ 实际上只更改数组中的值。一直复制它会变得非常耗时。
我是一个初学者,尤其是 C++,所以我真的不知道在哪里可以找到正确的信息,以及使用什么关键字。我们当然欢迎您提出如何做的想法,但我们也非常感谢您提供一些信息链接。
最好的,
【问题讨论】:
【参考方案1】:您可以使用 python C/C++ API 构建 C++ python 包装模块:
https://docs.python.org/2/extending/extending.html
我将使用提供服务的 python API 创建一个 C++ 模块 (dataUpdater
),让我们调用它,update
,它应该接收您想要加载数据的 Python 对象。
在您的 Python 端,每当我想从 C++ 加载数据时,我都会调用 dataUpdater.update
编辑:
其他选项是让您的 C++ 模块表现得像提供数据访问服务的数据结构,例如:
getValueAt(index)
setValueAt(index)
getSize()
并在 python 端使用它:
for i in xrange(dataUpdater.getSize()):
val = dataUpdater.getValueAt(i)
...
【讨论】:
我已经这样做了。我还可以在 C++ 和 Python 之间传输值和数组。但是我如何确保数组在从 Python 发送到 C++ 并返回时不会被复制? @renger 在这种情况下,使用您的 C++ 模块作为您的数据存储库,从而提供数组访问服务而不是更新方法。例如:getElementAtIndex(i)
、getSize()
。这个你的 C++ 接口更像是一个数据结构本身。
@renger 有两种解决方案:您可以将所有数据保留在 C++ 端,并在 Python 端每次需要时从 Python 调用它,可能通过实现序列协议它。 (对于多维数组,这可能非常复杂。)或者您可以使用numpy
,将numpy
数组作为单个参数传递给您的函数,并使用缓冲区协议获取(连续) 数据及其维度,并在 C++ 端工作。
我喜欢 C++ 模块作为数据结构的想法。然而,我想生成初始的“随机”星系,最好使用 numpy。那还有可能吗?还是我不必要地使一切复杂化了?
@renger 在这种情况下,您将需要使用生成的数据更新“数据结构”,除非您的 C++ 实现在 C++ 端管理的 numpy 结构中表示其内容,否则必须进行复制使用 Numpy C API。【参考方案2】:
您应该完全检查有关此问题的 Python 文档:
https://docs.python.org/2/extending/
记住文档,您可以定义一个新类型;假设 stars 是一个双精度数组:
typedef struct
PyObject_HEAD
double * Stars;
Galaxy;
然后定义数学运算方法...(python doc)
static PyObject* Galaxy_calc(Galaxy *self, PyObject *args)
double * Star_temp;
/* Your Array is referenced by self->Stars*/
Star_temp = self->Stars;
/* Do the math in C++ */
// All necessary calculations go here.
;
在定义的新类型(Galaxy)中包含这些方法相当容易,您只需设置变量:
static PyMethodDef Galaxy_methods[] =
"calc", (PyCFunction)Galaxy_calc, METH_VARARGS,"Performs stelar calculations.",
NULL /* Sentinel */
;
static PyMemberDef Galaxy_members[] =
"Stars", T_OBJECT_EX, offsetof(Galaxy, Galaxy), 0, "Galaxy Stars",
NULL /* Sentinel */
;
现在只需在
下的适当位置包含 Galaxy_methods 变量static PyTypeObject Galaxy_GalaxyType =
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"Galaxy.Galaxy ", /*tp_name*/
sizeof(Galaxy), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Galaxy_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"Galaxy objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Galaxy_methods, /* tp_methods */
Galaxy_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Galaxy_init, /* tp_init */
0, /* tp_alloc */
Galaxy_new, /* tp_new */
;
使用上面提到的python文档来实现new、alloc、dealloc和init方法(这些都很简单),就搞定了!
【讨论】:
他还需要访问器函数来访问 Python 端的数组。 (我怀疑使用numpy
会更容易,而且C++ 中只有一个函数,它获取numpy
的底层内存并使用它。)
我在玩 Pablo Fransisco Perez 的想法,即使用 C++ 端作为数据结构。但是在 python 中创建一个初始的“随机”星系比在 C++ 中容易得多。创建后,我可能会将它作为普通列表传递,因为我真的不知道 ctypes 是否支持 numpy 数组,正如 James Kanze 指出的那样。【参考方案3】:
正确执行此操作实际上非常复杂。首先你
应该使用包numpy
作为Python 中的数组。然后,
您将定义一个 C 接口,如中所述
https://docs.python.org/2/c-api/。 (这是参考手册,
所以你可能想阅读并尝试
https://docs.python.org/2/extending/index.html 首先。)大多数
重要的是,您将要使用缓冲区接口
(https://docs.python.org/2/c-api/buffer.html#bufferobjects)
访问numpy
数组。
ctypes
似乎对连续数组有一些支持
好吧,但我没有这方面的经验。如果你做任何处理
但是,您需要使用 Python 端的数组
numpy
,我认为 ctypes
不会支持。
【讨论】:
Python端不会做任何处理,只有初始化。如果 ctypes 不支持 numpy,我可以将我的随机 numpy 数组转换为列表。由于启动只需进行一次,因此速度不是问题。我认为有关缓冲区接口的文档会很有帮助。谢谢。【参考方案4】:这是关于如何使用 Boost.Python 完成此任务的另一个建议。
让我们将代码组织在 3 个文件中:一个 setup.py
负责编译扩展代码,一个仅使用扩展代码的 Python 脚本,以及扩展代码本身:
.
├── galaxy.cpp
├── main.py
└── setup.py
galaxy.cpp
:请注意,不处理异常,因此您可以通过分配给尚未初始化的 Star 和其他 C++ 怪异来产生分段错误。如果您修改此代码,请注意始终将 BOOST_PYTHON_MODULE 命名为文件本身。
#include <vector>
#include <boost/python.hpp>
class Star
public:
Star(double mass): mass(mass)
bool set_mass(double given_mass)
this->mass = given_mass;
return true;
private:
double mass;
;
class Galaxy
public:
Galaxy(const boost::python::list& masses)
for (size_t i = 0; i < len(masses); i++)
double mass = boost::python::extract<double>(masses[i]);
stars.push_back(Star(mass));
bool update(int star_number, double mass)
return this->stars[star_number].set_mass(mass);
private:
std::vector<Star> stars;
;
BOOST_PYTHON_MODULE(galaxy)
using namespace boost::python;
class_<Galaxy>("Galaxy", init< boost::python::list >())
.def("update", &Galaxy::update)
;
setup.py
:注意我的机器上已经使用 Macports 安装了 Boost;您可能需要在 include_dirs
变量中调整可以找到它的路径。
from distutils.core import setup
from distutils.extension import Extension
setup(name="galaxies",
ext_modules=[
Extension(
"galaxy", ["galaxy.cpp"],
include_dirs=["/opt/local/include"],
libraries=["boost_python-mt"])])
最后,使用Galaxy
对象在main.py
中执行您需要的任何操作。请注意,在此示例中,对象是从 Python 列表构造的(这意味着您实际上在 Python 和 C++ 之间至少传递了一次数组),但这不是必须的:您可以让 C++ 代码读取数据文件,并从 Python 传递它的路径。
import galaxy
sombrero = galaxy.Galaxy([0.1, 22.3, 33.4])
sombrero.update(0, 24.5)
下面是编译和运行示例的方法:
$ python setup.py build_ext --inplace && python main.py
【讨论】:
【参考方案5】:而且总是这样:
http://www.boost.org/doc/libs/1_55_0/libs/python/doc/
“欢迎使用 Boost.Python 的第 2 版,这是一个 C++ 库,可实现 C++ 和 Python 编程语言之间的无缝互操作性。”
【讨论】:
以上是关于使用 C++ 和 Python 访问数组的主要内容,如果未能解决你的问题,请参考以下文章
python 和 ctypes 访问具有嵌套结构的 c++ 类
C++ 等价于 Python 的带有比较的数组访问 (array[condition] = value)