Question

I'm trying to use Python as a scripting language for a C++ project of mine, so I reached for Boost.Python to make the task less tedious. I have a C++ class (ScriptSystem) which is responsible for holding references to and executing Python scripts.

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

The Python scripts are supposed to be subclasses of a wrapped C++ interface named IScript.

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

I managed to expose the ScriptSystem class and the IScript interface to Python, and it almost sorta kinda works. I can create new instances of IScript and pass them to ScriptSystem without any trouble. I can create subclasses which override tick(self) and as long as they don't define a constructor I'm good to go.

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

I can add data attributes to the class, but adding __init__(self) causes problems. For example, this code snippet executes just fine:

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

But this code fails:

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

So, I guess my question is: What is happening here?! I don't understand why adding a constructor to a Python subclass of Script (the python wrapper for IScript) causes an argument mismatch error.

Était-ce utile?

La solution

When a derived class provides an __init__ method, then it must explicitly call its base class __init__ methods to ensure proper initialization of the base class part of the instance. In this case, the Script part of an instance of Two is not properly initialized, causing Boost.Python is fail dispatching based on the C++ types.

To resolve the problem, consider changing:

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

to:

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

Here is a complete minimal example:

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

Interactive usage:

>>> 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())
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top