Is there some way to create proxy for pybind11-exported class ? #3411
-
Hi, What we wantWe want to create proxy class of pybind11-exported class at python level, so we can enhance these exported class with python code. Because we want to code in python as much as possible, to leverage python's simplicity and power. What we have done.We have exported a class import _wrap_module
def _Animal_print_me(self, extra_info):
self._print_me() # exported by pybind11
print(extra_info)
_wrap_module.Animal.print_me = _Animal_print_me By doing this, we also injected this method to all subclass transparently, e.g., A c++ function QuestionAs you see, the solution we used is not fancy, so I am wondering if there is some better solution. Also, we had used an inheriting solution like below, but abandoned it soon, import _wrap_module
class Animal(_wrap_module.Animal):
def __init__(): ...
def print_me(): .... This introduced some new problems, the first one is what we care
Sincerely |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
@edimetia3d that is a very good question! I was recently working on stuff and came up with this example. Before that I was using similar technique as you mentioned, "monkey-patching" all of the classes methods to the extend of patching classes with classes (🤯). Here you have cpp header and source, very simple ones: #include <iostream>
#include <string>
namespace base
{
class Animal
{
public:
Animal() = default;
Animal(std::string name);
void print();
protected:
std::string m_name;
};
class Dog : public Animal
{
public:
Dog(std::string name, size_t age);
size_t get_age();
private:
size_t m_age;
};
} // namespace base
#include "code.hpp"
#include <pybind11/pybind11.h>
namespace base
{
Animal::Animal(std::string name) : m_name(name){};
void Animal::print()
{
std::cout << "I am " << Animal::m_name << std::endl;
}
Dog::Dog(std::string name, size_t age) : Animal(name), m_age(age){};
size_t Dog::get_age()
{
return m_age;
}
}
namespace py = pybind11;
PYBIND11_MODULE(mymodule, module)
{
py::class_<base::Animal> animal(module, "Animal");
animal.def(py::init<std::string>());
animal.def("print", &base::Animal::print);
py::class_<base::Dog, base::Animal> dog(module, "Dog");
dog.def(py::init<std::string, size_t>());
dog.def("get_age", &base::Dog::get_age);
} And corresponding python code: import mymodule
class Dog(mymodule.Dog):
def print(self):
super().print() # mymodule.Dog.print(self)
print("I am a pug.")
def woof(self):
self.print()
print("I have {} years, woof!".format(self.get_age()))
animal = mymodule.Animal("Capybara")
dog = Dog("Lebron", 6)
animal.print()
print("=== Just print:")
dog.print()
print("=== Look at it:")
dog.woof() Results from terminal:
As you see it is actually possible to preserve parent's class methods and override them with extra code. However in more complex code bases it will be difficult to adjust C++ code to your actual needs, keep that in mind. Inheritance of C++ classes is done according to documentation, please take a look at it: https://pybind11.readthedocs.io/en/stable/classes.html#inheritance-and-automatic-downcasting The "cherry-on-top" is using However I would advise to use different naming for the classes wrapped in pybind and the ones created on python side. I know that module name will probably be different and so on. But it can create confusion in project. I would advise to use Hope this helps you! 🚀 PS: calling |
Beta Was this translation helpful? Give feedback.
-
Here, we had found a solution. I want to first show what we had achieved.Given the We created two proxy classes at python side, which will hold their own wrapped object by using the These proxy classes will follow the Proxy pattern, they could be used in any pybind11 exported function that requires an raw object. And more, any pybind11 exported function will return the proxy object instead of raw object. By using this, we could completely hide the pybind11-exported types, and extend these pybind11-exported types with pure python code in a more structural way. import _zoo
def real_type(RealT):
def deco(cls):
cls.RawType = RealT
return cls
return deco
@real_type(_zoo.Animal)
class Animal:
def __init__(self):
self._raw_obj = self.RawType()
def animal_new_method(self):
print(f"animal new method with {self._raw_obj}")
@real_type(_zoo.Dog)
class Dog(Animal):
def dog_new_method(self):
print(f"dog new method with {self._raw_obj}")
# case 1, Create Python object directly
animal = Animal()
animal.animal_new_method()
_zoo.AccessAnimal(animal) # wrapper object could be used with pybind11 exported method directly
_zoo.AccessAnimal(animal._raw_obj) # same as before
dog = Dog()
dog.animal_new_method() # inherit works
dog.dog_new_method()
_zoo.AccessAnimal(dog)
_zoo.AccessAnimal(dog._raw_obj) # same as before
_zoo.AccessDog(dog) # wrapper object could be used with pybind11 exported method directly
_zoo.AccessDog(dog._raw_obj) # same as before
# case 2, Return Python object from C++
new_animal = _zoo.CreateAnimal()
assert isinstance(new_animal, Animal)
assert isinstance(new_animal._raw_obj, _zoo.Animal)
new_dog = _zoo.CreateDog()
assert isinstance(new_dog, Dog)
assert isinstance(new_dog._raw_obj, _zoo.Dog) And the c++ pybind11 code is just based on the bulit-in
|
Beta Was this translation helpful? Give feedback.
Here, we had found a solution.
I want to first show what we had achieved.
Given the
_zoo
is a simple pybind11 module that hasAnimal
andDog
class.We created two proxy classes at python side, which will hold their own wrapped object by using the
has-a
logical, notis-a
.These proxy classes will follow the Proxy pattern, they could be used in any pybind11 exported function that requires an raw object.
And more, any pybind11 exported function will return the proxy object instead of raw object.
By using this, we could completely hide the pybind11-exported types, and extend these pybind11-exported types with pure python code in a more structural way.