-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Factory class doesn't allow derived classes to be created #1120
Comments
i think the problem is the cast in line 17: #16 std::shared_ptr<Animal> operator()() { py::object animal = obj();
#17 return animal.cast< std::shared_ptr<Animal> >();} what you are actually doing is converting an object created in python-side to some type in C++ side, which i do not think works in this simple way. yes, pybind11 supports converting between python object and c++ object for function arguments and return values, but with some book keeping codes, especially with creating proxy python object and properly handles reference counting. i think the so, for your use case, i think the proper way is do not perform the cast and just return the py::object operator()() { return obj(); } however, this way is not very type-safe though. |
Thanks, you got me on the right track. It would be nice to have the same kind of boost::python functionality that is able to deal with inheritance, but I can subclass the factory easily enough and bind that instead of the original one. |
I'm reopening this because I need to get that factory working on the C++ side. As it stands, I have two factories. If I am working in C++, I use an Animal factory which can be used in C++, or, through my bindings, in python. If I am working in python, I use a pybind11::object factory which can ONLY be used in python, because of the casting issue seen above. So, to go back to my original question: can we get this working, preferably without a workaround like above? |
I may be missing something, but I think you can solve this by always creating the new instance through Python via its Here's an example: myclone.cpp: #include <iostream>
#include <pybind11/pybind11.h>
namespace py = pybind11;
struct Foobar {
Foobar() {}
virtual ~Foobar() {}
virtual void speak() { std::cout << "I'm mute :(\n"; }
};
struct FoobarTrampoline : Foobar {
using Foobar::Foobar;
void speak() override { PYBIND11_OVERLOAD(void, Foobar, speak, ); }
};
PYBIND11_MODULE(myclone, m) {
py::class_<Foobar, FoobarTrampoline>(m, "Foobar")
.def(py::init<>())
.def("speak", &Foobar::speak)
;
m.def("clone_foo", [](py::object foobar) {
return foobar.attr("__class__")();
});
m.def("make_it_speak", [](Foobar &f) { f.speak(); });
} pyclone.py: from myclone import Foobar, clone_foo, make_it_speak
class MyFoobar(Foobar):
def __init__(self, *args, **kwargs):
Foobar.__init__(self, *args, **kwargs)
def speak(self):
print("Woof!")
a = MyFoobar()
b = Foobar()
c = clone_foo(a)
d = clone_foo(b)
for i in [a, b, c, d]:
print(type(i))
i.speak()
make_it_speak(i) Output:
|
I had that working on python previously, but the issue is doing the same in C++. I've tried dealing with a corresponding factory method in C++ itself rather than just in python, and that hasn't been working. I can write up a better example later you can actually run, but in the interest of time here's a quick example
A very similar problem, but one that can't be solved quite so easily. If I try going a bit fancier (like your suggestion, calling the python To complicate things further, these factories contain functions that return the desired class, and those functions don't have to be the default constructor. Those functions could mess around with member parameters before returning the object, for example. So it'd be best if I stick with the original object output by the factory (cast into the appropriate type) rather than falling back on trying to use the default constructor. |
Try casting it to a reference or pointer rather than a value: auto &foobar_in_cpp = foobar_from_python.cast<Foobar &>(); and if you need to combine it with a factory, do the factory call via Python side (via invoking |
Those were some of my first thoughts, and what I'm seeing is
leads to a segfault. foobar_in_cpp is non-negative, and no pybind11::cast_error is thrown, but it looks like it's not properly casting.
leads to "I'm mute"
similarly segfaults. |
I think the segfault is coming from something else; I don't see any segfaults if I amend my example to |
The issue isn't when I'm trying to run something in the pybind11 module, it's when I'm trying to send it back to C++. For example
works, it gives the expected
It'll first produce the |
That needs to be:
otherwise you're trying to cast the "clone_foo" attribute itself (rather than the result of calling it) to a Foobar pointer. |
It was correct in my actual code, there was just an editing error in my previous comment. |
I have not had time to fully process the entirety of this issue, but it looks like this is an aliasing / truncation issue due to the Python interpreter losing references on Pythonic portion of the instance, and leaving the C++ portion out in the cold all alone. Can you look at this PR and let me know if that actually might solve your issue? (The caveat is having to be beholden to a specific type of holder) I ask because ultimately our project may want something similar; a factory method that can be called on the C++ side, generating Python-derived objects that were registered with the factory. All that being said, have y'all already discussed the workaround of having a Python-specific base class / proxy, which stores its own |
Just to check, can I ask why you closed this? Did you come to a resolution in your code, or is this already covered by above issue? |
I know this is kind of an old issue, but I've encountered the same situation, so I think it could be useful to share how I handled it there. std::shared_ptr<Animal> createAnimal(py::object class) {
py::object animal = class();
return animal.cast< std::shared_ptr<Animal> >();
} is that the shared pointer seems to only be a soft reference to the object std::shared_ptr<Animal> createAnimal(py::object class) {
py::object* animal = new py::object(class());
return std::shared_ptr<Animal>(animal->cast< Animal* >());
} The trick here is to cast to a raw pointer instead of a shared pointer, because otherwise, the python reference that |
Er, I've since forgotten the intricacies on this one, but I do think this might be a form of object slicing happening here: @pvallet In your case, it works b/c you've allocated |
How is it leaking? The destructor of |
I think it's this line:
I don't see |
|
Er, true for The more nuanced part is When you call
For reference: pybind11/include/pybind11/pybind11.h Lines 1328 to 1380 in d96c345
pybind11/include/pybind11/pytypes.h Lines 220 to 240 in d96c345
|
Ok, I understand, thank you for the explanations. Basically, I was testing that the py::object pointer was deleted by casting it again to its underlying C++ type. I didn't realize that only the underlying type and not the python container was deleted. A bit counter intuitive to me since virtual calls to normal functions are resolved but not to destructors, it seems. I guess I'll have to dig into pybind11 code base to understand this issue better. |
Sounds good! And TBH, your current workaround is probably more or less what you may want. I have a solution in our little fork of pybind, but it's a tad bit messy, and doesn't do well in Python 3.8 ;) |
Ok, I got it to work using aliasing constructors, thanks to this github issue that was related #1389 std::shared_ptr<Animal> createAnimal(py::object pyClass) {
std::shared_ptr<py::object> pyObject = std::make_shared<py::object>(pyClass());
return std::shared_ptr<Animal>(pyObject, pyObject->cast<Animal*>());
} Basically, the python object holder is kept alive for as long as its contained object is alive. Unless I'm missing something, this should be memory safe. It just feels like the cast operator should be able to handle that itself. |
Ooh, nice! I was typing out that I thought there'd be some weird double-delete w/ a reference cycle, but reading through the SO link and the issue discussion, but nah, it looks pretty solid! (Still trying to think of edge cases, but so far have come up with none!) |
Note: This is most likely a duplicate of #1333. |
I'm trying to bind a factory class which takes in a constructor function and can later use that function to create an instance of the desired object. This should be working with inherited classes as well. In C++ it works fine, but in Python it looks like I'm losing the derived type. The derived class is created, deleted, and then an instance of the base class is returned.
Here are example bindings, keeping it as close to our original code as possible.
example.cpp
And then some sample python showing where it doesn't work as I expect
import example
The text was updated successfully, but these errors were encountered: