使用 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 传递到 C++ 的数组中未映射的内存访问

python 和 ctypes 访问具有嵌套结构的 c++ 类

在 python swig 中读取 c++ 2d 数组

C++ 等价于 Python 的带有比较的数组访问 (array[condition] = value)

如何使用 ctypes 将数组从 C++ 函数返回到 Python

AttributeError:python:未定义符号:使用 ctypes 从 Python 访问 C++ 函数时