Search code examples
pythonc++multiple-inheritancesuperboost-python

Boost.Python: how to get super() methods called?


I have a module with two C++ classes exposed which both have a method foo():

struct MyClassA{
    void foo() { std::cout << "MyClassA::foo()" << std::endl; }
};

struct MyClassB{
    void foo() { std::cout << "MyClassB::foo()" << std::endl; }
};

BOOST_PYTHON_MODULE(my_module){
    class_<MyClassA>("MyClassA", init<>()).def("foo", &MyClassA::foo);
    class_<MyClassB>("MyClassB", init<>()).def("foo", &MyClassB::foo);
}

In Python, I create a class that is derived from both classes:

from my_module import MyClassA, MyClassB

class Derived(MyClassA, MyClassB):
    def foo(self):
        super().foo()  # should be unnessessary - doesn't work anyway

a = MyClassA()
a.foo()  # works
b = MyClassB()
b.foo()  # works
d = Derived()
d.foo()  # only prints 'MyClassA::foo()'

Now I'd love to have d.foo() call MyClassA.foo() as well as MyClassB.foo(). But while Derived.mro() looks good:

[<class '__main__.Derived'>, <class 'my_module.MyClassA'>, <class 'my_module.MyClassB'>, <class 'Boost.Python.instance'>, <class 'object'>]

.. only MyClassA.foo() gets called.

How do I make the C++ methods call their super() methods? And does that work for __init__() as well?


Solution

  • My current approach (adapted from this answer) needs some sort of wrapper which handles the super()-calls manually. The resulting classes don't need any special handling any more:

    C++ code:

    struct MyClassA{
        void foo() { std::cout << "MyClassA::foo()" << std::endl; }
    };
    
    struct MyClassB{
        void foo() { std::cout << "MyClassB::foo()" << std::endl; }
    };
    
    BOOST_PYTHON_MODULE(my_module){
        class_<MyClassA>("MyClassACpp", init<>()).def("foo", &MyClassA::foo);
        class_<MyClassB>("MyClassBCpp", init<>()).def("foo", &MyClassB::foo);
    }
    

    wrapper:

    from my_module import MyClassACpp, MyClassBCpp
    
    def call_super(cls, instance, method, *args):
        mro = instance.__class__.mro()
        for next_class in mro[mro.index(cls) + 1:]:
            if not hasattr(next_class, method):
                continue
            if next_class.__module__ in {'Boost.Python', 'builtins'}:
                continue
            getattr(next_class, method)(instance, *args)
            if next_class.__module__ != 'my_module':
                break
    
    class MyClassA(MyClassACpp):
        def __init__(self):
            call_super(MyClassA, self, '__init__')
            print('MyClassA.__init__()')
    
        def foo(self):
            call_super(MyClassA, self, 'foo')
    
    class MyClassB(MyClassBCpp):
        def __init__(self):
            call_super(MyClassB, self, '__init__')
            print('MyClassB.__init__()')
    
        def foo(self):
            call_super(MyClassB, self, 'foo')
    

    usage:

    class Derived(MyClassA, MyClassB):
        def foo(self):
            super().foo()
    
    d = Derived()
    d.foo()
    

    output:

    MyClassA.__init__()
    MyClassB.__init__()
    MyClassA::foo()
    MyClassB::foo()