-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keyword arguments mismatch with C++ function arguments #785
Comments
If I leave only one overload and remove all keyword arguments like this: m.def("well_path_ident", &well_path_ident); then function called properly without exceptions and all arguments are passed correctly. |
The issue is most likely stemming from inheriting from You probably want to use composition (i.e. use a local I'll add a |
(I also couldn't reproduce the skipping-kwargs issue with regular array_t arguments, so I suspect it's the same issue). |
The holder casters assume but don't check that a `holder<type>`'s `type` is really a `type_caster_base<type>`; this adds a static_assert to make sure this is really the case, to turn things like `std::shared_ptr<array>` into a compilation failure. Fixes pybind#785
I understand your point about all wrapped Python types being already reference counted via But still, if used properly, inheritance from |
Yes, you're definitely right. That's what #786 will fix (by making it a compilation failure). |
The holder casters assume but don't check that a `holder<type>`'s `type` is really a `type_caster_base<type>`; this adds a static_assert to make sure this is really the case, to turn things like `std::shared_ptr<array>` into a compilation failure. Fixes #785
Thanks for the report. This should cause a compilation failure now rather than an internal error. I've filed #787 separately to track properly supporting holders around converting types. |
I've reproduced the issue with this test case; investigating it now: #include <pybind11/pybind11.h>
struct mytype {};
using mysp = std::shared_ptr<mytype>;
NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
template <> struct type_caster<mysp> {
public:
bool load(handle src, bool convert) { return false; }
static handle cast(const mysp&, return_value_policy, handle) { return handle(); }
operator mysp() { return value; }
template <typename> using cast_op_type = mysp;
static PYBIND11_DESCR name() { return _("foo"); }
private:
mysp value;
};
NAMESPACE_END(detail)
NAMESPACE_END(pybind11)
namespace py = pybind11;
PYBIND11_PLUGIN(example)
{
py::module m("example");
m.def("f", [](int, mysp, int) {}, py::arg("a"), py::arg("b"), py::arg("c") = 2);
return m.ptr();
} |
... which gives the rather interesting (and wrong) docstring:
|
Yes, that's exactly what I've got. |
Aha, there's a bug in the Eigen type caster with named arguments (which you ended up copying). The static PYBIND11_DESCR name() { return _("foo"); } should be static PYBIND11_DESCR name() { return type_descr(_("foo")); } |
The test suite code that I wrote even has some of the same errors in it that I never noticed (the 'arg0: ' should be on the argument, not the return type!). |
The second bug (fixed in PR #791) affects the doc string in eigen (and if you make the same change, as I mentioned above, should fix it in your class as well). I made sure that it doesn't actually affect the passing of named arguments themselves: it just affects the way they are displayed in the error message/docstring. That PR (and equivalent change for you) should resolve the docstring; I'm hoping that it plus the change to make the You aren't hitting the added static asserts because you're adding a type caster for a shared pointer, rather than the pointed-at type of the shared_ptr (which is what I had assumed from your initial report), and so you shouldn't hit that code path at all. On the plus side, the change to composition probably isn't necessary: your type caster isn't for a type that inherits from |
TLDR: did that (uentity/pybind11@c5d1198) but issue is not resolved. Docstring is fixed now. If I pass all arguments (including ones with default values specified), then function get called without errors. If I omit at least one argument from the end of args list, then I hit Maybe default value for an array casing the issue? My C++ function accepts As of composition code - I've made ~60% of work, but took a pause until you can find out the solution :). And I was thinking the same way -- if I wrap shared_ptr, then I can leave inheritance. My other array traits are also inherited from containers (so I can for ex. easily import constructors), so for better consistency and less chance of errors I'd better leave current approach as is. |
I'm all for tracking this down, but I'm out of ideas. I don't think the If you can narrow this down into a relatively small, self-contained test case, I'll investigate some more, but right now it's a bit too much. |
@jagerman, I've nailed down the issue. It indeed was related to code path assigning default Exception in Python fires when calling I'll prepare test case tomorrow and post here. |
What's the |
Ahh error was a little bit deeper than I described. The main cause of the issue was not in So, the issue caused by the following line: Array aref = Array::ensure(src); where You was right about tricky maintaining of non-compositional code. |
So I am right to conclude that pybind is okay here, it was just an error handling issue? (Nevertheless, we did find and fix two other bugs in the process, so it was all worthwhile). |
@jagerman pybind11 is okay here, but maybe you'll consider to replace array_t(const object &o) : array(raw_array_t(o.ptr()), stolen_t{}) {
if (!m_ptr) throw error_already_set();
} with array_t(const object &o) : array(raw_array_t(o.ptr()), stolen_t{}) {
if (!m_ptr && PyErr_Occurred()) throw error_already_set();
} ? You may argue that array with
If |
I can trigger it with an explicit empty object (which is different from None!), like this: py::array_t<double>(py::object()) and this really does indicate a failure of the caller: we're being asked to convert a generic That said, we need to fix the error message to something informative. |
#794 should address this (by giving a more informative error message). |
@jagerman what Python version are you using? I'm on 2.7.x (still) and I can tell for sure that Here's the test code that I inherited from your example. #include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <iostream>
namespace py = pybind11;
using namespace pybind11::literals;
using ulong = std::size_t;
using Array = py::array_t< unsigned long, py::array::forcecast >;
struct mytype {
Array val_;
mytype(const Array& src) : val_(src) {}
mytype& operator()(const Array& src) {
val_ = src;
return *this;
}
};
using mysp = std::shared_ptr<mytype>;
NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
template <> struct type_caster<mysp> {
public:
bool load(handle src, bool convert) {
bool need_copy = !isinstance< Array >(src);
if(!need_copy) {
Array aref = reinterpret_borrow< Array >(src);
if(aref && aref.writeable()) {
value = std::make_shared< mytype >(std::move(aref));
return true;
}
}
//if (!convert) return false;
Array aref = Array::ensure(src);
if(aref.ptr() == 0)
std::cout << "OMG! I'm null, don't try to dereference me!" << std::endl;
if(!aref) return false;
value = std::make_shared< mytype >(std::move(aref));
return true;
}
static handle cast(const mysp&, return_value_policy, handle) {
return handle();
}
operator mysp() { return value; }
template <typename> using cast_op_type = mysp;
static PYBIND11_DESCR name() { return type_descr(_("foo")); }
private:
mysp value;
};
NAMESPACE_END(detail)
NAMESPACE_END(pybind11)
mysp f(
ulong nx, ulong ny, mysp coord, mysp zcorn,
mysp well_info, bool include_well_nodes = true, const char* strat_traits = "online_tops",
const ulong min_split_threshold = 0, mysp hit_idx = nullptr
) {
return mysp();
}
PYBIND11_PLUGIN(example)
{
py::module m("example");
m.def("f", &f,
"nx"_a, "ny"_a, "coord"_a, "zcorn"_a, "well_info"_a, "include_well_nodes"_a = true,
"mesh_traits"_a = "online_tops", "min_split_threshold"_a = 0, "hit_idx"_a = nullptr);
//m.def("f", [](int, int, mysp) {}, py::arg("a"), py::arg("c") = 2, py::arg("b") = nullptr);
return m.ptr();
} When I call |
Yes, you get a |
OK, I think we nailed this issue to the bones, so it's time to just close it :) |
I have a C++ function with the following signature:
Here
ulong
isstd::size_t
, andspv_float, spv_ulong
arestd::shared_ptr
pointers to my custom array type which is inherited frompybind11::array_t
. I've written custom converters for it that are very similar toEigen
binding code. Converters are working as expected in my tests.Next, I export the function above with the following snippet:
Everything compiles just fine!
pybind11
correctly counts arguments ofwell_path_ident
-- if I make a mistake (for ex. forget one argument) it will notify me with an error at compile time.But after module is loaded in Python here's what I get with
help(my_module)
command:(I have one more overload of this function, but it has the same issue).
As you can see, keyword arguments "skip"
numpy.ndarray[float64]
(spv_float) for some reason, and starting from "coord" they bind to wrong C++ function arguments.I feel that this is related in some way to custom type conversion code, but what really can cause such issue?
Btw, when I try to call this function from Python, I get an exception:
And actual C++ function isn't called, I can't get into it in debugger.
The text was updated successfully, but these errors were encountered: