将构造函数添加到Boost.Python子类会导致参数不匹配错误

时间:2014-01-22 03:09:37

标签: python c++ boost-python

我尝试使用Python作为我的C ++项目的脚本语言,所以我选择Boost.Python来减少任务的繁琐。我有一个C ++类(ScriptSystem),它负责保存对Python脚本的引用和执行。

namespace py = boost::python;

class ScriptSystem : public System
{
    public:
    virtual void execute_scripts() override;

    void addToPyGlobals(const OtherSystemsPtr&);
    void addScript(IScript*);

    private:
    std::vector<IScript *> scripts;
    py::dict python_globals;        
};

Python脚本应该是名为IScript的包装C ++接口的子类。

class IScript
{
    public:
    virtual ~IScript() {}

    // This function is meant to be overridden. It prints
    // to the console for debugging purposes only.
    virtual void tick() const {
        cout << "Default Implementation." << endl;
    }
};

我设法将ScriptSystem类和IScript接口暴露给Python,它几乎有点有用。我可以创建新的IScript实例并将它们传递给ScriptSystem而不会有任何麻烦。我可以创建覆盖tick(self)的子类,只要他们不定义构造函数我就可以了。

namespace py = boost::python;

class ScriptWrapper : public Script::IScript, public py::wrapper<Script::IScript>
{
    public:
    void tick()const override
    {
        // Check for Python override
        if(py::override f = this->get_override("tick")) {
            std::cout << "Found Python override"
                      << std::endl;
            f();
            return;
        }

        // If there is no override, call the default implementation
        std::cout << "No Python override found, calling C++ implementation." 
                  << std::endl;
        this->Script::IScript::tick();
        return;
    }

    void default_tick() const {
        return this->Script::IScript::tick();
    }
};


BOOST_PYTHON_MODULE(_script)
{
    py::class_<ScriptSystem, boost::noncopyable>("ScriptSystem")
        .def("add_script", &ScriptSystem::addScript);

    py::class_<ScriptWrapper, boost::noncopyable>("Script")
        .def("tick", &Script::IScript::tick, &ScriptWrapper::default_tick);

    py::implicitly_convertible<ScriptWrapper*, Script::IScript*>();
}

我可以向类添加数据属性,但添加__init__(self)会导致问题。例如,此代码段执行得很好:

(RemoteShell)
>>> from script import Script
>>> class One(Script):
...     def tick(self):
...             print "I'm a script!"
...             print "My value is {}".format(One.value)
... 
>>> One.value = 5
>>> one = One()              
>>> script_system.add_script(one)

但是这段代码失败了:

(RemoteShell)
>>> from script import Script
>>> class Two(Script):
...     def __init__(self):
...             self.count = 0
...     def tick(self):
...             if self.count % 2 == 0:
...                     print "Tick"
...             else:
...                     print "Tock"
...             self.count += 1
... 
>>> two = Two()
>>> script_system.add_script(two)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
ArgumentError: Python argument types in
    ScriptSystem.add_script(ScriptSystem, Two)
did not match C++ signature:
    add_script(ScriptSystem {lvalue}, Script::IScript*)

所以,我想我的问题是:这里发生了什么?!我不明白为什么将一个构造函数添加到Script的Python子类(IScript的python包装器)会导致参数不匹配错误。

1 个答案:

答案 0 :(得分:2)

当派生类提供__init__方法时,它必须显式调用其基类__init__方法,以确保正确初始化实例的基类部分。在这种情况下,Script实例的Two部分未正确初始化,导致Boost.Python基于C ++类型进行失败调度。

要解决此问题,请考虑更改:

class Two(Script):
    def __init__(self):
        self.count = 0

为:

class Two(Script):
    def __init__(self):
        self.count = 0
        Script.__init__(self)

这是一个完整的最小例子:

#include <boost/python.hpp>

/// @brief Mockup type.
class spam {};

/// @brief Mockup function that takes spam types.
void action(spam*) {}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<spam>("Spam");
  python::def("action", &action);
}

交互式使用:

>>> import example
>>> example.action(example.Spam())
>>> class MoreSpam(example.Spam):
...     def __init__(self):
...         pass
... 
>>> example.action(MoreSpam())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    example.action(MoreSpam)
did not match C++ signature:
    action(spam*)
>>> class EvenMoreSpam(example.Spam):
...     def __init__(self):
...         example.Spam.__init__(self)
... 
>>> example.action(EvenMoreSpam())