使用pybind11在类方法中调用嵌入式函数

时间:2018-07-17 00:45:19

标签: c++ python-3.x

我正在使用pybind11嵌入python的c ++应用程序,尝试从类方法中调用嵌入式函数时遇到了一个问题。

从这里开始是我的绑定:

#ifdef _DEBUG
#undef _DEBUG
#include <python.h>
#define _DEBUG
#else
#include <python.h>
#endif
#include <embed.h>

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

void DebugInfo(std::string string_)
{
    String LogMessage_(string_.c_str());
    LOGINFO(LogMessage_);
}

PYBIND11_EMBEDDED_MODULE(Test, m) {
    m.def("DebugInfo", &DebugInfo, "Posts message to DEBUGINFO");
}

然后我可以拥有一个.py文件,其中包含:

import Test
test.DebugInfo("I'm a lumberjack and that's OK")

它可以很好地进行调试

当我尝试从类方法中调用它时,麻烦就开始了。

import Test
class PyTest(object):
    def __init__(self):
        test.DebugInfo("I'm a lumberjack and that's OK")

test = PyTest()

运行此命令时,它会针对cast.h引发异常,尤其是针对此函数一部分的1985行:

template <return_value_policy policy>
class unpacking_collector {
public:
    template <typename... Ts>
    explicit unpacking_collector(Ts &&...values) {
        // Tuples aren't (easily) resizable so a list is needed for collection,
        // but the actual function call strictly requires a tuple.
        auto args_list = list();
        int _[] = { 0, (process(args_list, std::forward<Ts>(values)), 0)... };
        ignore_unused(_);

        m_args = std::move(args_list);
    }

    const tuple &args() const & { return m_args; }
    const dict &kwargs() const & { return m_kwargs; }

    tuple args() && { return std::move(m_args); }
    dict kwargs() && { return std::move(m_kwargs); }

    /// Call a Python function and pass the collected arguments
    object call(PyObject *ptr) const {
        PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr());
        if (!result)
            throw error_already_set();  //EXCEPTION THROWS HERE!
        return reinterpret_steal<object>(result);
    }

因为这可能很重要,所以这就是我从主应用程序中调用整个事情的方式

//Start the Python Interpreter
py::scoped_interpreter guard{};
//Python variables
py::object thing_;
std::string test_py = Import_File("test.py");
auto locals = py::dict();
py::exec(test_py, py::globals(), locals);
thing_ = locals["test"].cast<py::object>();
thing_.attr("start")();

以及test.py的内容

import Test
class PyTest(object):
    def __init__(self, message = "Test Object initialized"):
        self.message = message
        iterstr = str(self.iter)
        message = self.message + iterstr
        self.iter = 0
        Test.DebugInfo(message)
    def start(self):
        self.message = "Starting Python Object"
        self.iter = self.iter + 1
        iterstr = str(self.iter)
        message = self.message + iterstr
        Test.DebugInfo(message)
    def update(self):
        self.message = "Python Object Update Cycle:"
        self.iter = self.iter + 1
        iterstr = str(self.iter)
        message = self.message + iterstr
        Test.DebugInfo(message)

test = PyTest()

我不确定是否遇到了pybind11的局限性,即其中的错误,还是只是把整个事情搞砸了。

任何见识将不胜感激。

2 个答案:

答案 0 :(得分:2)

这也是pybind11的问题,在这里:https://github.com/pybind/pybind11/issues/1452

我同时遇到了这个问题和问题,但是我发现了。将其复制到这里,供以后偶然遇到此问题的任何人

基本上,您实际上不需要py::dict的空白locals;这会引起各种各样的问题。如果您希望嵌入来自文档或测试的示例代码,则locals值始终会复制全局范围。

请参阅:

* https://pybind11.readthedocs.io/en/stable/advanced/embedding.html

* https://github.com/pybind/pybind11/blob/master/tests/test_embed/test_interpreter.cpp#L57

您的选择是复制全局范围,或者在这种情况下,只需不传递locals

py::scoped_interpreter guard{}; 
auto globals = py::globals();
py::exec(test_py, globals);
thing_ = globals["Object"].cast<py::object>(); 
thing_.attr("start")();

对于顶级代码(不在任何模块内部),globals变量看起来像是在此范围内保存值。

答案 1 :(得分:0)

因此,经过一些实验,我发现问题是由pybind无法检测到功能范围之外的导入模块引起的。

import foo

def bar():
  foo.func()

将始终导致错误。但是,

def bar():
  import foo
  foo.func()

将按预期运行。