-
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
How to correctly set default argument "none" for pybind11::array_t ? #1953
Comments
I have the same problem for setting default argument "none" for pybind11::list and pybind11::dict. |
@WeinaJi That is a strange inconsistency indeed. However, the If you want to accept The same goes for |
Reopening to have the conversion from #include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
using array = py::array_t<double, pybind11::array::c_style | pybind11::array::forcecast>;
PYBIND11_MODULE(example, m) {
m.def("f", [](array a) { py::print(a); }, py::arg("a") = py::none());
} >>> import example
>>> example.f([])
[]
>>> example.f()
nan |
You're taking the following constructor: array_t(const object &o) : array(raw_array_t(o.ptr()), stolen_t{}) {
if (!m_ptr) throw error_already_set();
} The static PyObject *raw_array_t(PyObject *ptr) {
if (ptr == nullptr) {
PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
return nullptr;
}
return detail::npy_api::get().PyArray_FromAny_(
ptr, dtype::of<T>().release().ptr(), 0, 0,
detail::npy_api::NPY_ARRAY_ENSUREARRAY_ | ExtraFlags, nullptr);
} Since we know we just took a reference from static PyObject *raw_array_t(PyObject *ptr) {
return py::detail::npy_api::get().PyArray_FromAny_(
ptr, py::dtype::of<double>().release().ptr(), 0, 0,
py::detail::npy_api::NPY_ARRAY_ENSUREARRAY_ | py::array::forcecast, nullptr);
} Now...
Two hours later...
Wow, that was a fun rabbit hole. I'm pretty sure this After a lot of inlining and manual evaluation, here's how the above looks like: static PyObject *raw_array_t(PyObject *ptr) {
auto& npy_api_ref = py::detail::npy_api::get();
return npy_api_ref.PyArray_FromAny_(
ptr,
npy_api_ref.PyArray_DescrFromType_(12), // 12 == NPY_DOUBLE_
0,
0,
0x50,
nullptr);
}
int main() {
Py_Initialize();
PyObject* h2 = ::raw_array_t(Py_None);
PyObject* sys = PyImport_ImportModule("sys");
PyObject* out = PyObject_GetAttrString(sys, "stdout");
PyObject* write = PyObject_GetAttrString(out, "write");
PyObject* t = PyTuple_New(1);
PyTuple_SetItem(t, 0, PyObject_Str(h2));
PyObject_Call(write, t, NULL);
Py_Finalize();
} At this point:
We can do better!
Another 40 minutes later...
#include <Python.h>
enum functions {
API_PyArray_DescrFromType = 45,
API_PyArray_FromAny = 69,
};
struct npy_api {
PyObject *(*PyArray_FromAny_) (PyObject *, PyObject *, int, int, int, PyObject *);
PyObject *(*PyArray_DescrFromType_)(int);
};
static struct npy_api lookup() {
PyObject* multiarray = PyImport_ImportModule("numpy.core.multiarray");
PyObject* array_api = PyObject_GetAttrString(multiarray, "_ARRAY_API");
void **api_ptr = (void **) PyCapsule_GetPointer(array_api, NULL);
struct npy_api api;
api.PyArray_DescrFromType_ = api_ptr[API_PyArray_DescrFromType];
api.PyArray_FromAny_ = api_ptr[API_PyArray_FromAny];
return api;
}
static PyObject *raw_array_t(PyObject *ptr) {
const struct npy_api npy_api_ref = lookup();
return npy_api_ref.PyArray_FromAny_(
ptr,
// 12 == NPY_DOUBLE_
npy_api_ref.PyArray_DescrFromType_(12),
0,
0,
0x50,
NULL);
}
int main() {
Py_Initialize();
PyObject* h2 = raw_array_t(Py_None);
PyObject* sys = PyImport_ImportModule("sys");
PyObject* out = PyObject_GetAttrString(sys, "stdout");
PyObject* write = PyObject_GetAttrString(out, "write");
PyObject* t = PyTuple_New(1);
PyTuple_SetItem(t, 0, PyObject_Str(h2));
PyObject_Call(write, t, NULL);
Py_Finalize();
} This time, it really is pure C. Now is this a bug? I don't know. Perhaps NumPy considers this expected behaviour.
A much faster way of confirming this is regular numpy behaviour is |
I shall now perform an honorary closing of this issue. |
Thanks for the prompt reply. I found the same suggestion that I should use std::optional in the pybind doc. It works perfect. But I have to say the current way to allow/prohibiting none argument is confusing, since I can set and compile the |
Yes, that's kind of true. The use of |
With C++17's Say you want this function: def add(a, b=None):
# Assuming a, b are int.
if b is None:
return a
else:
return a + b Here's the equivalent C++ implementation: m.def("add",
[](int a, std::optional<int> b) {
if (!b.has_value()) {
return a;
} else {
return a + b.value();
}
},
py::arg("a"), py::arg("b") = py::none()
); In python, this function can be called with: add(1)
add(1, 2)
add(1, b=2)
add(1, b=None) |
Dear Pybind,
I am working with tag v2.4.3. I would like to bind a structure containing an array of int and set the array's default value to "None" in the constructor.
My simple unit test is
And my python script is
for which I got the error message
However, if I change the c++ code to bind an array of float or double and set the default to nullptr, the script above is running fine without error.
Is None argument allowed for pybind11::array, and why is there difference between type int and float/double ?
Many thanks in advance!
Regards
The text was updated successfully, but these errors were encountered: