Подтвердить что ты не робот

Как я могу реализовать класс С++ в Python, который вызывается С++?

У меня есть интерфейс класса, написанный на С++. У меня есть несколько классов, которые реализуют этот интерфейс, также написанный на С++. Они называются в контексте более крупной программы на С++, которая по сути реализует "основную". Я хочу иметь возможность писать реализации этого интерфейса в Python и разрешать их использовать в контексте более крупной программы на С++, как если бы они были просто написаны на С++.

Было много написано о взаимодействии python и С++, но я не могу понять, как делать то, что я хочу. Ближайший я могу найти здесь: http://www.cs.brown.edu/~jwicks/boost/libs/python/doc/tutorial/doc/html/python/exposing.html#python.class_virtual_functions, но это не совсем правильно.

Чтобы быть более конкретным, предположим, что у меня есть существующий интерфейс С++, что-то вроде:

// 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

Затем, в моем коде на С++, я хочу сказать что-то вроде:

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

Я надеюсь, что это достаточно ясно;)

4b9b3361

Ответ 1

На этот ответ две части. Сначала вам нужно разоблачить свой интерфейс в Python таким образом, чтобы реализация Python могла переопределять его части по своему усмотрению. Затем вам нужно показать свою С++-программу (в 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 в ваше приложение, т.е. вы начинаете excetion в Python, а не в int main() в С++. Это довольно просто добавить, что позже.

Сначала выполняется полиморфизм кросс-языка:

%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 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)

С этим я тогда смог скомпилировать и запустить это:

swig -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;
}

В основном это стандартный встраивание Python в другое приложение. Он работает и дает то, что вы надеетесь увидеть также:

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

Последний кусок головоломки способен преобразовать PyObject*, который вы получаете от создания экземпляра в Python, в myif *. SWIG снова делает это достаточно простым.

Сначала нам нужно попросить 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

Затем добавим вспомогательную функцию для преобразования PyObject* в myif* в наш main.cc:

#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;
}

Наконец, мы должны скомпилировать main.cc с -DSWIG_TYPE_TABLE=myif, и это дает:

./main
11

Ответ 2

Минимальный пример; обратите внимание, что это осложняется тем, что Base не является чисто виртуальным. Там мы идем:

  • 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)
      ;
    }
    
  • 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()
    
  • Makefile

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

И результат:

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

где вы можете увидеть, как fooBase() (не виртуальная функция С++) вызывает виртуальную foo(), которая разрешает переопределение независимо от того, в С++ или python. Вы можете получить класс из Base в С++, и он будет работать точно так же.

EDIT (извлечение объекта С++):

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))();

Создайте py::object из PyObject* и используйте py::extract для запроса, соответствует ли объект python тому, что вы пытаетесь извлечь: PyObject* obj; py::extract<Base> extractor(py::object(obj)); if(!extractor.check()) /* error */; Base& b=extractor();

Ответ 3

Цитата http://wiki.python.org/moin/boost.python/Inheritance

"Boost.Python также позволяет нам представлять отношения наследования С++, чтобы переносить производные классы, когда значения, указатели или ссылки на базовый класс ожидаются как аргументы".

Есть примеры виртуальных функций, которые разрешают первую часть (класс с классом MyCl (myif))

Для конкретных примеров это http://wiki.python.org/moin/boost.python/OverridableVirtualFunctions

Для строки myif c = MyCl(); вам нужно выставить свой python (модуль) на С++. Примеры здесь http://wiki.python.org/moin/boost.python/EmbeddingPython

Ответ 4

Основываясь на (очень полезном) ответе от Eudoxos, я взял его код и расширил его таким образом, что теперь есть встроенный интерпретатор со встроенным модуль.

Этот ответ является эквивалентом Boost.Python моего ответа на SWIG.

Заголовочный файл 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 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();
  }
}

Ключевая часть, которую я добавил здесь, выше и выше "как мне вставлять Python с помощью Boost.Python?" и "как я могу расширить Python с помощью Boost.python?" (на который ответил Eudoxos) ответ на вопрос "Как мне сделать оба сразу в одной программе?". Решение этого заключается в вызове PyImport_AppendInittab, который принимает функцию инициализации, которая обычно вызывается при загрузке модуля и регистрирует ее как встроенный модуль. Таким образом, когда mycl.py говорит import myif, он завершает импорт встроенного модуля Boost.Python.

Ответ 6

Нет реального способа напрямую связать код С++ с 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);
}

Поскольку вы строите с использованием С++, 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. Это вам.

Здесь документ Python для ctypes:

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

Кроме того, встроенная dll должна находиться в той же папке, что и ваш интерфейс Python (почему бы и нет?).

Мне любопытно, почему, почему вы хотите вызвать Python из С++ вместо прямого вызова реализации С++?