【问题标题】:How can I implement a C++ class in Python, to be called by C++?如何在 Python 中实现 C++ 类,由 C++ 调用?
【发布时间】:2012-02-20 21:03:40
【问题描述】:

我有一个用 C++ 编写的类接口。我有一些实现这个接口的类也是用 C++ 编写的。这些是在更大的 C++ 程序的上下文中调用的,该程序本质上实现了“main”。我希望能够用 Python 编写这个接口的实现,并允许它们在更大的 C++ 程序的上下文中使用,就好像它们只是用 C++ 编写的一样。

关于连接 python 和 C++ 的文章很多,但我无法完全弄清楚如何做我想做的事。我能找到的最接近的是:http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions,但这不太正确。

更具体地说,假设我有一个现有的 C++ 接口定义如下:

// myif.h
class myif {
   public:
     virtual float myfunc(float a);
};

我想要做的是:

// mycl.py
... some magic python stuff ...
class MyCl(myif):
  def myfunc(a):
    return a*2

然后,回到我的 C++ 代码中,我希望能够这样说:

// mymain.cc
void main(...) {
  ... some magic c++ stuff ...
  myif c = MyCl();  // get the python class
  cout << c.myfunc(5) << endl;  // should print 10
}

我希望这足够清楚;)

【问题讨论】:

  • “我希望这足够清楚” ... C++ 包裹在 python 中,然后再次包裹在 C++ 中?
  • 您希望通过构建此基础架构实现什么目标?
  • @AJG85,不是“包裹”。他要求能够在 Python 中继承 C++ 类,然后在 C++ 中使用它。 (嗯!)
  • 这听起来像是 SIP (riverbankcomputing.co.uk/software/sip/intro) 应该做的——很多使用 PyQT 的工作都涉及到继承 Qt 的 C++ 类。也就是说,我不知道它作为一个独立的工具有多有用。
  • @AJG85 假设您有一个需要回调对象来通知客户端的 C++ 库。这个回调是使用 c++ 抽象类实现的,应该实现、创建然后传递给库以供稍后调用。如果这个库要在 Python 中使用,你应该能够首先在 Python 中实现接口并将其传递给 C++ 库。这可以是一个用例。

标签: c++ python swig boost-python


【解决方案1】:

这个答案有两个部分。首先,您需要以允许 Python 实现随意覆盖其中一部分的方式在 Python 中公开您的接口。然后你需要展示你的 C++ 程序(在main 如何调用 Python。


将现有接口暴露给 Python:

使用 SWIG 很容易完成第一部分。我稍微修改了您的示例场景以解决一些问题并添加了一个额外的测试功能:

// myif.h
class myif {
   public:
     virtual float myfunc(float a) = 0;
};

inline void runCode(myif *inst) {
  std::cout << inst->myfunc(5) << std::endl;
}

现在我将在不将 Python 嵌入您的应用程序的情况下查看问题,即您在 Python 中启动异常,而不是在 C++ 中的 int main() 中启动。不过,稍后添加它相当简单。

首先获取cross-language polymorphism working:

%module(directors="1") module

// We need to include myif.h in the SWIG generated C++ file
%{
#include <iostream>
#include "myif.h"
%}

// Enable cross-language polymorphism in the SWIG wrapper. 
// It's pretty slow so not enable by default
%feature("director") myif;

// Tell swig to wrap everything in myif.h
%include "myif.h"

为此,我们在全球范围内启用了 SWIG 的导演功能,专门针对我们的界面。不过,其余部分都是相当标准的 SWIG。

我写了一个测试 Python 实现:

import module

class MyCl(module.myif):
  def __init__(self):
    module.myif.__init__(self)
  def myfunc(self,a):
    return a*2.0

cl = MyCl()

print cl.myfunc(100.0)

module.runCode(cl)

这样我就可以编译并运行它了:

痛饮 -python -c++ -Wall myif.i g++ -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7 python mycl.py 200.0 10

正是您希望从该测试中看到的。


在应用程序中嵌入 Python:

接下来我们需要实现您的 mymain.cc 的真实版本。我已经把它可能看起来的草图放在一起:

#include <iostream>
#include "myif.h"
#include <Python.h>

int main()
{
  Py_Initialize();

  const double input = 5.0;

  PyObject *main = PyImport_AddModule("__main__");
  PyObject *dict = PyModule_GetDict(main);
  PySys_SetPath(".");
  PyObject *module = PyImport_Import(PyString_FromString("mycl"));
  PyModule_AddObject(main, "mycl", module);

  PyObject *instance = PyRun_String("mycl.MyCl()", Py_eval_input, dict, dict);
  PyObject *result = PyObject_CallMethod(instance, "myfunc", (char *)"(O)" ,PyFloat_FromDouble(input));

  PyObject *error = PyErr_Occurred();
  if (error) {
    std::cerr << "Error occured in PyRun_String" << std::endl;
    PyErr_Print();
  }

  double ret = PyFloat_AsDouble(result);
  std::cout << ret << std::endl;

  Py_Finalize();
  return 0;
}

它基本上只是标准的embedding Python in another application。它有效,并且提供了您希望看到的内容:

g++ -Wall -Wextra -I/usr/include/python2.7 main.cc -o main -lpython2.7 。/主要的 200.0 10 10

难题的最后一块是能够将您在 Python 中创建实例时获得的 PyObject* 转换为 myif *。 SWIG 再次使这变得相当简单。

首先,我们需要让 SWIG 为我们在头文件中公开其运行时。我们通过额外调用 SWIG 来做到这一点:

痛饮 -Wall -c++ -python -external-runtime runtime.h

接下来,我们需要重新编译我们的 SWIG 模块,显式地提供 SWIG 知道的名称类型表,以便我们可以从 main.cc 中查找它。我们重新编译 .so 使用:

g++ -DSWIG_TYPE_TABLE=myif -Wall -Wextra -shared -o _module.so myif_wrap.cxx -I/usr/include/python2.7 -lpython2.7

然后我们在 main.cc 中添加一个辅助函数,用于将 PyObject* 转换为 myif*

#include "runtime.h"
// runtime.h was generated by SWIG for us with the second call we made

myif *python2interface(PyObject *obj) {
  void *argp1 = 0;
  swig_type_info * pTypeInfo = SWIG_TypeQuery("myif *");

  const int res = SWIG_ConvertPtr(obj, &argp1,pTypeInfo, 0);
  if (!SWIG_IsOK(res)) {
    abort();
  }
  return reinterpret_cast<myif*>(argp1);
}

现在我们可以在main()中使用它:

int main()
{
  Py_Initialize();

  const double input = 5.5;

  PySys_SetPath(".");
  PyObject *module = PyImport_ImportModule("mycl");

  PyObject *cls = PyObject_GetAttrString(module, "MyCl");
  PyObject *instance = PyObject_CallFunctionObjArgs(cls, NULL);

  myif *inst = python2interface(instance);
  std::cout << inst->myfunc(input) << std::endl;

  Py_XDECREF(instance);
  Py_XDECREF(cls);

  Py_Finalize();
  return 0;
}

最后我们必须用-DSWIG_TYPE_TABLE=myif 编译 main.cc,这给出:

。/主要的 11

【讨论】:

  • @JonoRR - 已修复。我使用模块句柄而不是它的字典作为 PyRun 的参数。
  • 所需的所有源代码和命令都包含在我的答案中,但我也将全部内容放在网上:static.lislan.org.uk/~ajw/pyvirt.tar.gz
  • 你能评论一下创建的对象应该如何被正确破坏吗?快速测试似乎表明有必要运行Py_DECREF(instance),并且简单地运行delete inst 不会释放Python 对象。我说的对吗?
  • @job 假设 python“拥有”底层本机对象 Py_DECREF 是必要且足以删除它的。如果你删除了接口并且 python 拥有它,如果你不撤销所有权,你最终会被双重删除。
  • 感谢您的解释!对于像我一样希望 C++ 拥有对象所有权并能够以 C++ 方式删除对象的人,您必须执行以下操作:1) 通过运行 mycl.MyCl().__disown__() 创建实例,2) 将实例转换为通过调用python2interface 来调用您的界面,3) 在PyObject 实例上运行Py_DECREF,4) 完成对象后,使用delete 销毁它,这也应该销毁Python 对象(假设有在 Python 中不再引用它,如果你使用 Flexo 的方法创建它就不应该有)。
【解决方案2】:

最小的例子;请注意,Base 不是纯虚拟的事实使情况变得复杂。我们去:

  1. baz.cpp:

    #include<string>
    #include<boost/python.hpp>
    using std::string;
    namespace py=boost::python;
    
    struct Base{
      virtual string foo() const { return "Base.foo"; }
      // fooBase is non-virtual, calling it from anywhere (c++ or python)
      // will go through c++ dispatch
      string fooBase() const { return foo(); }
    };
    struct BaseWrapper: Base, py::wrapper<Base>{
      string foo() const{
        // if Base were abstract (non-instantiable in python), then
        // there would be only this->get_override("foo")() here
        //
        // if called on a class which overrides foo in python
        if(this->get_override("foo")) return this->get_override("foo")();
        // no override in python; happens if Base(Wrapper) is instantiated directly
        else return Base::foo();
      }
    };
    
    BOOST_PYTHON_MODULE(baz){
      py::class_<BaseWrapper,boost::noncopyable>("Base")
        .def("foo",&Base::foo)
        .def("fooBase",&Base::fooBase)
      ;
    }
    
  2. bar.py

    import sys
    sys.path.append('.')
    import baz
    
    class PyDerived(baz.Base):
      def foo(self): return 'PyDerived.foo'
    
    base=baz.Base()
    der=PyDerived()
    print base.foo(), base.fooBase()
    print der.foo(), der.fooBase()
    
  3. 生成文件

    default:
           g++ -shared -fPIC -o baz.so baz.cpp -lboost_python `pkg-config python --cflags`
    

结果是:

Base.foo Base.foo
PyDerived.foo PyDerived.foo

在这里您可以看到fooBase()(非虚拟c++ 函数)如何调用虚拟foo(),无论在c++ 还是python 中,它都会解析为覆盖。你可以在 c++ 中从 Base 派生一个类,它的工作原理是一样的。

编辑(提取 c++ 对象):

PyObject* obj; // given
py::object pyObj(obj); // wrap as boost::python object (cheap)
py::extract<Base> ex(pyObj); 
if(ex.check()){ // types are compatible
  Base& b=ex(); // get the wrapped object
  // ...
} else {
  // error
}

// shorter, thrwos when conversion not possible
Base &b=py::extract<Base>(py::object(obj))();

PyObject* 构造py::object 并使用py::extract 查询python 对象是否与您要提取的内容匹配:PyObject* obj; py::extract&lt;Base&gt; extractor(py::object(obj)); if(!extractor.check()) /* error */; Base&amp; b=extractor();

【讨论】:

  • 这看起来很不错——你能把嵌入解释器的等效 Boost.Python 添加到我的示例中,它使用 myif *python2interface(PyObject *obj); 获取 PyObject 并将其转换回C++接口的实现? (这正是我对赏金的想法)
  • 我已经为此奖励了你,谢谢!我还将您的解决方案扩展为同时嵌入和扩展,我已将其作为第二个答案发布。
【解决方案3】:

引用http://wiki.python.org/moin/boost.python/Inheritance

“Boost.Python 还允许我们表示 C++ 继承关系,以便可以传递封装的派生类,其中值、指针或对基类的引用应作为参数。”

有一些虚函数的例子可以解决第一部分(MyCl(myif) 类的那个)

对于这样做的具体示例,http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

对于行 myif c = MyCl();你需要将你的 python(模块)暴露给 C++。这里有例子http://wiki.python.org/moin/boost.python/EmbeddingPython

【讨论】:

  • 你能把它扩展成一个完整的例子吗?如果这能激发你的灵感,我会悬赏它:)
  • 我不是反对者,但我怀疑这个答案缺乏细节和略微投机的性质是其背后的原因。这当然是我提供赏金的原因。
【解决方案4】:

基于the (very helpful) answer by Eudoxos,我采用了他的代码并对其进行了扩展,现在有一个嵌入式解释器,带有一个内置模块。

这个答案是 my SWIG based answer 的 Boost.Python 等价物。

头文件myif.h:

class myif {
public:
  virtual float myfunc(float a) const { return 0; }
  virtual ~myif() {}
};

基本上与问题相同,但默认实现为myfunc 和虚拟析构函数。

对于Python的实现,MyCl.py我的问题基本一样:

import myif

class MyCl(myif.myif):
  def myfunc(self,a): 
    return a*2.0

然后离开 mymain.cc,其中大部分是基于 Eudoxos 的回答:

#include <boost/python.hpp>
#include <iostream>
#include "myif.h"

using namespace boost::python;

// This is basically Eudoxos's answer:
struct MyIfWrapper: myif, wrapper<myif>{
  float myfunc(float a) const {
    if(this->get_override("myfunc")) 
      return this->get_override("myfunc")(a);
    else 
      return myif::myfunc(a);
  }
};

BOOST_PYTHON_MODULE(myif){
  class_<MyIfWrapper,boost::noncopyable>("myif")
    .def("myfunc",&myif::myfunc)
  ;
}
// End answer by Eudoxos

int main( int argc, char ** argv ) {
  try {
    // Tell python that "myif" is a built-in module
    PyImport_AppendInittab("myif", initmyif);
    // Set up embedded Python interpreter:
    Py_Initialize();

    object main_module = import("__main__");
    object main_namespace = main_module.attr("__dict__");

    PySys_SetPath(".");
    main_namespace["mycl"] = import("mycl");

    // Create the Python object with an eval()
    object obj = eval("mycl.MyCl()", main_namespace);

    // Find the base C++ type for the Python object (from Eudoxos)
    const myif &b=extract<myif>(obj)();
    std::cout << b.myfunc(5) << std::endl;

  } catch( error_already_set ) {
    PyErr_Print();
  }
}

我在这里添加的关键部分,超越了“如何使用 Boost.Python 嵌入 Python?”和“如何使用 Boost.python 扩展 Python?” (由 Eudoxos 回答)是对“我如何在同一个程序中同时执行这两个操作?”这个问题的答案。对此的解决方案在于PyImport_AppendInittab 调用,它采用在加载模块时通常会调用的初始化函数并将其注册为内置模块。因此,当 mycl.py 显示 import myif 时,它最终会导入内置的 Boost.Python 模块。

【讨论】:

    【解决方案5】:

    看看 Boost Python,它是连接 C++ 和 Python 的最通用和最强大的工具。

    http://www.boost.org/doc/libs/1_48_0/libs/python/doc/

    【讨论】:

    • 它仅与 Python C API 一样强大,但它绝对是一个很棒的库。 One thing that I'm looking for 而你显然不能用 Boost 来定义 C++ 中的 Python 元类;不过,您可以使用 C API 做到这一点。
    【解决方案6】:

    没有真正的方法可以将 C++ 代码直接与 Python 接口。

    SWIG 确实处理了这个问题,但它构建了自己的包装器。

    我更喜欢 SWIG 的另一种选择是 ctypes,但要使用它,您需要创建一个 C 包装器。

    例如:

    // myif.h
    class myif {
       public:
         virtual float myfunc(float a);
    };
    

    像这样构建一个 C 包装器:

    extern "C" __declspec(dllexport) float myif_myfunc(myif* m, float a) {
        return m->myfunc(a);
    }
    

    由于您使用 C++ 构建,extern "C" 允许 C 链接,因此您可以轻松地从 dll 调用它,而 __declspec(dllexport) 允许从 dll 调用函数。

    在 Python 中:

    from ctypes import *
    from os.path import dirname
    
    dlldir = dirname(__file__)                      # this strips it to the directory only
    dlldir.replace( '\\', '\\\\' )                  # Replaces \ with \\ in dlldir
    lib = cdll.LoadLibrary(dlldir+'\\myif.dll')     # Loads from the full path to your module.
    
    # Just an alias for the void pointer for your class
    c_myif = c_void_p
    
    # This tells Python how to interpret the return type and arguments
    lib.myif_myfunc.argtypes = [ c_myif, c_float ]
    lib.myif_myfunc.restype  = c_float
    
    class MyCl(myif):
        def __init__:
            # Assume you wrapped a constructor for myif in C
            self.obj = lib.myif_newmyif(None)
    
        def myfunc(a):
            return lib.myif_myfunc(self.obj, a)
    

    虽然 SWIG 为您完成了所有这些工作,但您几乎没有空间可以随意修改内容,而不会因为重新生成 SWIG 包装器时必须重做的所有更改而感到沮丧。

    ctypes 的一个问题是它不处理 STL 结构,因为它是为 C 设计的。SWIG 会为您处理这个,但您可以自己将它包装在 C 中。这取决于您。

    这是 ctypes 的 Python 文档:

    http://docs.python.org/library/ctypes.html

    另外,构建的 dll 应该和你的 Python 接口在同一个文件夹中(为什么不呢?)。

    不过我很好奇,为什么要从 C++ 内部调用 Python 而不是直接调用 C++ 实现?

    【讨论】:

    • “您几乎没有空间可以随意修改内容,而不会因重新生成 SWIG 包装器时必须重做的所有更改而感到沮丧” - 您应该 从不手动编辑 SWIG 生成的文件。总有一种方法可以让 SWIG 生成你想要的代码。
    • 就赏金而言,这个答案并没有增加我所能看到的太多 - 这并没有解决嵌入问题,并且“我如何传递从基本接口派生的 Python 类型的实例回到 C++ 函数问题。我可以编写一个代理来处理这个问题,但我正在寻找一种解决方案,该解决方案涉及编写 less 胶水或比我建议的 SWIG 更整洁的胶水。这似乎添加了更多手动编写的胶水,而不是解决在 C++ 中嵌入 Python 的问题。
    猜你喜欢
    • 1970-01-01
    • 2018-03-28
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多