Ссылка на выделенный объект C++ в pybind11

Я пытаюсь создать привязку python с помощью pybind11, которая ссылается на экземпляр C++, память которого обрабатывается на стороне C++. Вот пример кода:

import <pybind11/pybind11>

struct Dog {
    void bark() { printf("Bark!\n"); }
};

int main()
{
  auto dog = new Dog;
  Py_Initialize();
  initexample(); // Initialize the example python module for import 

  // TBD - Add binding  between dog and example.dog .

  PyRun_StringFlags("import example\n"
                    "\n"
                    "example.dog.bark()\n"  // Access the C++ allocated object dog.
                    , Py_file_input, main_dict, main_dict, NULL);
  Py_Finalize();
}

Я застрял в том, как создать связь между python example.dog и переменной C++ dog.

Я не могу использовать py:class_<Dog>.def(py::init<>()), так как это выделит новый экземпляр Dog, а это не то, что мне нужно.


person Dov Grobgeld    schedule 04.10.2016    source источник


Ответы (3)


Я нашел ответ на свой вопрос. Хитрость заключалась в сочетании следующих двух концепций:

  • Создайте независимую функцию, которая возвращает синглтон.
  • Создайте привязку к одноэлементному классу без привязки конструктора.

Следующий пример иллюстрирует технику:

#include <Python.h>
#include <pybind11/pybind11.h>

namespace py = pybind11;
using namespace pybind11::literals;

// Singleton to wrap
struct Singleton
{
  Singleton() : x(0) {}

  int exchange(int n)  // set x and return the old value
  {
    std::swap(n, x);
    return n;
  }

  // Singleton reference 
  static Singleton& instance()
  {
    static Singleton just_one;
    return just_one;
  }

  int x;
};

PYBIND11_PLUGIN(example) {
    py::module m("example", "pybind11 example plugin");

    // Use this function to get access to the singleton
    m.def("get_instance",
          &Singleton::instance,
          py::return_value_policy::reference,
          "Get reference to the singleton");

    // Declare the singleton methods
    py::class_<Singleton>(m, "Singleton")
      // No init!
      .def("exchange",
           &Singleton::exchange,
           "n"_a,
           "Exchange and return the current value"
           )
      ;

    return m.ptr();
}

int main(int argc, char **argv)
{
  Py_Initialize();

  PyObject* main_module = PyImport_AddModule("__main__");
  PyObject* main_dict = PyModule_GetDict(main_module);

  initexample();

  // Call singleton from c++
  Singleton::instance().exchange(999);

  // Populate the example class with two static pointers to our instance.
  if (PyRun_StringFlags("import example\n"
                        "\n"
                        "example.s1 = example.get_instance()\n"
                        "example.s2 = example.get_instance()\n",
                        Py_file_input, main_dict, main_dict, NULL) == nullptr)
      PyErr_Print();

  // Test referencing the singleton references
  if (PyRun_StringFlags("from example import *\n"
                        "\n"
                        "for i in range(3):\n"
                        "  print s1.exchange(i*2+1)\n"
                        "  print s2.exchange(i*2+2)\n"
                        "print dir(s1)\n"
                        "print help(s1.exchange)\n"
                        ,
                        Py_file_input, main_dict, main_dict, NULL) == nullptr)
      PyErr_Print();

  Py_Finalize();

  exit(0);
}
person Dov Grobgeld    schedule 07.10.2016

Начиная с Pybind11 v2.2.0 возможен другой подход с использованием пользовательской оболочки конструктора: методу инициализации python больше не нужно вызывать конструктор c++. Вы можете заставить его напрямую возвращать экземпляр C++ singleton.

Декларация в вашем случае может выглядеть так:

   // Declare the singleton methods
   py::class_<Singleton>(m, "Singleton")

      .def("init", [](){
          return std::unique_ptr<Singleton, py::nodelete>(&Singleton::instance());
      });

В питоне:

myInstance1 = Singleton()
myInstance2 = Singleton()

myInstance1 и myInstance2 указывают на один и тот же объект c++.

Это в основном тот же ответ, что и на этот другой вопрос.

person gg99    schedule 30.05.2018

Да, я знаю, что этот ответ довольно поздний, но решения, предоставленные ранее, либо устарели, либо решают проблему неясным образом.

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

После реализации, которая должна решить вашу проблему.

Первое, что вы заметите, это то, что мы используем заголовочный файл «embed.h». Это дает нам функции для создания встроенных модулей.

Ниже мы используем PYBIND11_EMBEDDED_MODULE вместо обычного PYBIND11_MODULE или устаревшего PYBIND11_PLUGIN. Это макрос специально для встраивания.

Следующая интересная часть — это типы, которые мы определяем для нашей структуры. Кроме DogType мы также используем shared_ptr<Dog>. Это очень важно для работы с экземплярами. Когда модуль main выходит из Scope и начинает очистку, он должен знать, что класс/структура имеет тип shared_ptr, иначе вы получите ошибку seg (необработанные указатели здесь не используются, я лично считаю, что это хорошо).

Последнее, на что следует обратить внимание, это то, что мы фактически используем класс pybind11::scoped_interpreter для нашего интерпретатора и не используем интерфейс Raw Python.

#include"pybind11\pybind11.h"
#include"pybind11\embed.h"

#include<iostream>

namespace py = pybind11;

struct Dog {
    void bark() { std::cout << "Bark!\n";  }
};

PYBIND11_EMBEDDED_MODULE(DogModule, m) {
    // look at the types! we have a shared_ptr<Dog>!
    py::class_<Dog, std::shared_ptr<Dog>>(m, "DogModule")
        .def("bark", &Dog::bark);
}


int main(int argc, char **argv) 
{
    // Create Python Interpreter
    py::scoped_interpreter guard;

    // Create Dog Instance
    std::shared_ptr<Dog> ptr = std::make_shared<Dog>();

    // Import the DogModule & Assign the instance to a name in python
    py::module main = py::module::import("__main__");
    main.import("DogModule");
    main.attr("dogInstance") = ptr;

    // Call the bark method from python
    py::exec("dogInstance.bark()");


    getchar();
    return 0;
}
person Simerax    schedule 26.06.2018