将 Python 类实例传递给 C++ 函数

Posted

技术标签:

【中文标题】将 Python 类实例传递给 C++ 函数【英文标题】:Passing a Python class instance into a C++ function 【发布时间】:2014-05-03 04:18:30 【问题描述】:

我有一个用 C++ 编写的内部库,我目前正在努力将其扩展到 Python。我在开始这项任务时考虑到了 Boost.Python,但我对替代方案持开放态度。

目前我有一个 C++ 函数需要接受一个 Python 类实例,然后使用这个对象的方法来执行某些任务。这个想法是让 Python 用户永远不需要处理 C++。他们应该从我将提供的 Python 模板/示例类创建这个 Python 对象,并使用我可以假设在我的 C++ 库中的预设方法名称。

暴露给 Python 用户的界面如下所示:

class Foo(object):

    def __init__(self, args):
        """create an instance of this class with instance-specific attributes"""

    def Bar1(self, a, b, c):
        """do something with the given integers a, b and c"""
        pass

    def Bar2(self, a, b, c):
        """do something else with the given integers a, b and c"""
        pass

import mylib

cheese = mylib.Wine()
cheese.do_something(Foo)

在 C++ 中,对应的代码如下:

#include <boost/python.h>
#include <Python.h>

class Wine 

public:  

    Wine() ;

    ~Wine() ;

    static void do_something(boost::python::object *Foo) 

        int a = 1;
        int b = 2;
        int c = 3;

        Foo->attr("Bar1")(a, b, c);
        Foo->attr("Bar2")(a, b, c);
    ;
;

BOOST_PYTHON_MODULE(mylib)

    using namespace boost::python;
    class_<Wine>("Wine")
        .def("do_something", &Wine::do_something);
;

我已经成功编译了这段代码,并验证了名为 Wine 的 C++ 类确实暴露给了 Python,并且我可以访问它的成员函数。如果我编写一个名为“greet()”的成员函数,它只返回“Hello, world!”,它就可以完美运行。

我需要在这里强调传递 Foo 实例的重要性。我没有奢侈地将 Foo 模块简单地导入 C++ 代码并在 C++ 中创建 Foo 的实例。我想从 Python 用户接收的对象具有我需要使用的属性,这些属性特定于实例,而不是类本身。

问题是我不知道如何将 Python 实例传递给 do_something,这样它就会在 C++ 中作为可调用的 boost::python::object 出现。上述代码返回如下 C++ 签名不匹配错误:

Boost.Python.ArgumentError: Python argument types in
    Wine.do_something(Wine, Foo)
did not match C++ signature:
    do_something(boost::python::api::object*)

两天在互联网上寻找答案没有取得任何进展。似乎有很多关于如何将 C++ 类传递给 Python 的信息,但我无法找到相反方向的信息。非常感谢这里的一些指导。

谢谢!

【问题讨论】:

【参考方案1】:

初始代码有两个错误:

Boost.Python 正在尝试将两个参数(selfFoo 的一个实例)传递给只接受一个参数的静态Wine::do_something() C++ 函数。为了解决这个问题,在公开Wine 类时,Python 的Wine.do_something() 成员函数需要通过boost::python::class_::staticmethod() 成员函数设置为静态。当作为静态方法公开时,Boost.Python 将不再传递 self 实例参数。 与 Python/C API 不同,其中指针通常用作对象的句柄 (PyObject*),Boost.Python 提供了更高级别的符号 boost::python::object 类,通常通过值或引用传递。在内部,该类与为PyObject 执行智能指针管理的boost::python::handle 交互。

这里是基于原代码的完整 Python 扩展:

#include <boost/python.hpp>

class Wine

public:  

  static void do_something(boost::python::object object)
  
    int a = 1;
    int b = 2;
    int c = 3;

    object.attr("Bar1")(a, b, c);
    object.attr("Bar2")(a, b, c);
  ;
;

BOOST_PYTHON_MODULE(example)

  namespace python = boost::python;
  python::class_<Wine>("Wine")
    .def("do_something", &Wine::do_something)
      .staticmethod("do_something")
    ;
;

互动使用:

>>> class Foo(object):
...     def Bar1(self, a, b, c):
...         print "Bar1", locals()
...     def Bar2(self, a, b, c):
...         print "Bar2", locals()
... 
>>> import example
>>> cheese = example.Wine()
>>> cheese.do_something(Foo())
Bar1 'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>
Bar2 'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>

【讨论】:

这非常有用。如果我的电脑回到前面,我会报告它的成功,但它肯定很有意义。一个问题。如果我理解正确,那么您的意思是 boost 对象类已经处理了指向 pyobject 的智能指针,因此接受对象而不是 object* 不会创建 Foo 的副本。如果是这样,那是个好消息,因为我们真的不想在这里克隆 Foo。它的目的是在实践中成为一个非常大的对象。 @AlpDener 很高兴它很有帮助。不会制作Foo 的副本。 Boost.Python 维护 Python 的参数传递语义,其中对象引用按值传递。有关生命周期和线程的更多信息(注意到您的其他评论提到了并行性),请考虑阅读this 答案。 我刚刚有机会在我的代码中实现这一点,我很高兴地报告它运行良好。还为您对线程的回复添加了书签,以防我们将来遇到这种情况。非常感谢!【参考方案2】:

要公开接受 Python 对象作为参数的方法,您应该使用 boost::python::object 而不是 boost::python::object *

void do_something(boost::python::object Foo)

要公开静态方法,请像普通函数一样公开它:def("do_something", Wine::do_something);

import mylib

# method
cheese = mylib.Wine()
foo = Foo()
cheese.do_something(foo)

#static method
foo = Foo()
mylib.do_something(foo)

【讨论】:

这会在 C++ 中复制 Foo 吗?这是非常不可取的。 Foo 对象旨在相当大(并且高度并行化)。我们希望 C++ 函数只引用这个 Python 对象。

以上是关于将 Python 类实例传递给 C++ 函数的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 Python/C API 将 Python 实例传递给 C++

将派生类传递给采用 SWIG Python 中的基类的函数

将派生自基类的类的实例传递给函数

如何将数组传递给构造函数或类?

将图像从 python 包装器传递给 c++ 函数

来自 C++ 的 QQuickItem 实例化和设置难题(不允许将任何内容传递给构造函数)