user3093226
user3093226

Reputation: 43

Subclassing C++ classes in Python with Pybind11 - can I have a virtual method taking kwargs?

in my C++/Pybind11 project I have a C++ class Foo that can be subclassed in Python. As usual, I went through the usual process of having a trampoline class PyFoo and the customary bindings.

However, now it turned out that it would be useful to have the start method of Foo to take key words arguments (pybind11::kwargs).

In my project, I also have a class Caller that holds a collection of Foos. Caller exposes a method start that calls the start method of the required foo, forwarding the kwargs to the Foo's start method.

However, calling Caller's start method from Python always generates the following error:

TypeError: start() takes 1 positional argument but 2 were given

Here's the complete code to reproduce the issue. Is this something possible and if so, what should I do to make it work?

File foo.h/cpp

#pragma once

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


// c++ class 
class Foo {
public:
    Foo() = default;

    virtual ~Foo() = default;

    virtual void start(pybind11::kwargs args) {}

    virtual void update(double) {}

};

// trampoline class
class PyFoo : public Foo {
public:
    using Foo::Foo;  // Inherit constructors

    void PyFoo::start(pybind11::kwargs args) override {
        std::cout << "mane\n";
        PYBIND11_OVERRIDE(
            void,                               // Return type
            Foo,                    // Parent class
            start,                              // Name of function in C++
            args
    );


    void PyFoo::update(double dt) {
 
        PYBIND11_OVERRIDE(
            void,                           // Return type
            Foo,                            // Parent class
            update,                         // Name of function in C++
            dt
    );

};


class Caller {
public:
    void start(int id, pybind11::kwargs args) {

        _controllers[id]->start(std::move(args));
    }
    void add(pybind11::object foo) {
        _objs.push_back(foo);
        _controllers.push_back(foo.cast<std::shared_ptr<Foo>>());
    }
private:
    std::vector<pybind11::object> _objs;
    std::vector<std::shared_ptr<Foo>> _controllers;

};

Here's the bindings:

namespace py = pybind11;

PYBIND11_MODULE(untitled3, m) {

    py::class_<Foo, PyFoo, std::shared_ptr<Foo>>(m, "Foo")
        .def(py::init<>());

    py::class_<Caller, std::shared_ptr<Caller>>(m, "Caller")
        .def(py::init<>())
        .def("add", &Caller::add)
        .def("start", &Caller::start);
}

Here's the Py code

import untitled3

class Bar(untitled3.Foo):

    def __init__(self):
        super().__init__()

    def start(self, **kwargs):
        print('ici')

    def update(self, dt):
        print('here')


c = untitled3.Caller()
c.add(Bar())
c.add(Bar())
c.start(1)

Upvotes: 0

Views: 37

Answers (0)

Related Questions