-
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
array: implement array resize #795
Conversation
include/pybind11/numpy.h
Outdated
@@ -158,6 +163,7 @@ struct npy_api { | |||
Py_ssize_t *, PyObject **, PyObject *); | |||
PyObject *(*PyArray_Squeeze_)(PyObject *); | |||
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); | |||
PyObject* (*PyArray_Resize_)(pybind11::detail::PyArray_Proxy*, PyArray_Dims*, int, int); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the first argument should just be a PyObject *
, to be consistent with the other function pointers (e.g. PyArray_Squeeze_
).
include/pybind11/numpy.h
Outdated
@@ -646,6 +654,26 @@ class array : public buffer { | |||
return reinterpret_steal<array>(api.PyArray_Squeeze_(m_ptr)); | |||
} | |||
|
|||
/// Resize array to given shape | |||
/// If refcheck is true, then resize will fail if > 1 references exists to this array | |||
void resize(const std::vector<size_t> &new_shape, bool refcheck = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I strongly feel that refcheck
should default to true
. Bypassing a reference check can be pretty dangerous, and should never be the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the comment here is a little bit wrong: resize fails with > 1 reference only if changing the total size. If the size doesn't change, a resize should be fine. (It's probably also worth changing the test to explicitly test this; at = a.transpose()
is a simple way to add a reference to a
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for refcheck=true
by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that in the Python API, refcheck defaults to True.
include/pybind11/numpy.h
Outdated
// try to resize, set order to -1 cause it's not used anyway | ||
new_array = detail::npy_api::get().PyArray_Resize_( | ||
detail::array_proxy(m_ptr), &d, int(refcheck), -1 | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than use raw pointers, better to use pybind::object
to make sure you get proper reference handling. This should work:
object new_array = reinterpret_steal<object>(detail::npy_api::get().PyArray_Resize_(
detail::array_proxy(m_ptr), &d, int(refcheck), -1
));
if (!new_array) throw error_already_set();
if (isinstance<array>(new_array)) {
*this = std::move(new_array);
}
The main difference, aside from simplifying the code a bit, is that when the return is PyNone, you still properly decref it at the end of the function when the object
is destroyed (unless you move it away first). Right now it leaks a reference--which probably isn't that big a deal with None, but still.
Somewhat strange to me about PyArray_Resize()
is that, looking at the source code, I can't see how it ever returns anything other than None (aside from NULL returned on error).
tests/test_numpy_array.py
Outdated
@@ -377,3 +377,18 @@ def test_array_unchecked_dyn_dims(msg): | |||
|
|||
assert proxy_auxiliaries2_dyn(z1) == [11, 11, True, 2, 8, 2, 2, 4, 32] | |||
assert proxy_auxiliaries2_dyn(z1) == array_auxiliaries2(z1) | |||
|
|||
def test_array_resize(msg): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should also test some failure conditions here, i.e. that you get the expected exception and exception message if attempting to resize a referenced array.
tests/test_numpy_array.cpp
Outdated
@@ -264,4 +264,24 @@ test_initializer numpy_array([](py::module &m) { | |||
sm.def("array_auxiliaries2", [](py::array_t<double> a) { | |||
return auxiliaries(a, a); | |||
}); | |||
|
|||
// resize array to 3D without changing size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3D
-> 2D
tests/test_numpy_array.cpp
Outdated
// resize array to 3D without changing size | ||
// should probably succeed | ||
sm.def("array_reshape2", [](py::array_t<double> a) { | ||
const size_t dim_sz(std::sqrt(a.size())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A self-commenting input validation test:
if (dim_sz * dim_sz != a.size())
throw std::domain_error("array_reshape2: input array total size is not a squared integer");
A few things to fix, but nothing major: this looks pretty good to me. |
There is also the possibility of simplifying the implementation so it doesn't need the Numpy C API: void resize(const std::vector<size_t> &new_shape, bool refcheck = true) {
attr("resize")(new_shape, refcheck);
} Which would be a bit slower because of the dynamic method lookup, but at the same time I don't think |
include/pybind11/numpy.h
Outdated
void resize(const std::vector<size_t> &new_shape, bool refcheck = false) { | ||
/// If refcheck is true and more that one reference exist to this array | ||
/// then resize will succeed only if it makes a reshape, i.e. original size doesn't change | ||
void resize(const std::vector<size_t> &new_shape, bool refcheck = true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of taking concrete containers (std::vector<size_t>
) like this--it's restrictive and something of a nuisance if you happen to have your shape in some other container. Just to be clear: this isn't your fault at all; what you wrote here is perfectly consistent with the current API. But I'm aiming to solve it with PR #788, which would allow you to change the signature to void resize(ShapeContainer new_shape, bool refcheck = true)
, where the ShapeContainer
is implicitly convertible from any sort of container, copy the contents into a std::vector<Py_intptr_t>
. That would also allow you to drop both the reinterpret_cast
and const_cast
from:
reinterpret_cast<Py_intptr_t*>(const_cast<size_t*>(new_shape.data())), int(new_shape.size())
replacing it with just:
new_shape->data(), int(new_shape.size())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've merged #788, so you should be able to switch the input to a ShapeContainer new_shape
now. (Unfortunately I also caused some conflicts--both had tests added in the same place--but I added a commit to hopefully resolve them).
(Minor: it's best to not have merge commits from master in feature branches, ideally this PR would get rebased or squashed) |
Yeah, it'll get squashed and rebased before being merged. |
include/pybind11/numpy.h
Outdated
@@ -129,6 +129,11 @@ struct npy_api { | |||
NPY_STRING_, NPY_UNICODE_, NPY_VOID_ | |||
}; | |||
|
|||
typedef struct { | |||
const Py_intptr_t *ptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to make ptr const
here instead of having const_cast<Py_intptr_t*>(new_shape->data())
later in resize()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just change the const ShapeContainer &new_shape
to ShapeContainer new_shape
; it's essentially always going to be passed by value (via implicit conversion), so the const reference doesn't gain anything.
Edit: the point being to keep the definition identical to the one numpy defines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
tests/test_numpy_array.cpp
Outdated
@@ -264,7 +264,28 @@ test_initializer numpy_array([](py::module &m) { | |||
return auxiliaries(a, a); | |||
}); | |||
|
|||
// Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object: | |||
// Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops
You can ignore the MSVC failure; that's something broken in appveyor (#792). |
Travis also didn't complete for some reason... |
I just restarted the failed job. |
Can you rebase this against current master? I have a feeling the travis-ci commit I merged today is going to cause some failures in the PyPy test here from some things numpy doesn't support when under PyPy (and so need to be skipped for PyPy). |
Rebased. |
Don't worry about 5.4.0; we don't support anything before 5.7 as it had several fixes we reported to get it working with pybind. The one test failure was exactly what I expected to fail; just skip that test under PyPy: move the failing assertions (basically anything with |
Wait, failed to build? That part should work (with 5.7 at least). |
Year, I see what you have done with disabling tests on PyPy in prev commits. I just wanted to check it myself before pushing. But as I can see now, you build tests on PyPy 5.7 with Python 2.7 and I tried to build against PyPy 5.7 with Python 3.5. That combination gives compile error:
|
Aha, looks like some Python 3.x compatibility issues in the Python 3 version of PyPy. There is no need to worry about it here. |
Oh, right, I forgot about the PyPy3 version. We currently only support PyPy2 5.7. |
Yes, I noticed that support is only for PyPy2 5.7. |
This looks good to me. There's a merge conflict right now. Just resolve that and I think it's good to merge. |
Can you do a rebase against master rather than a merge? It makes it easier to commit without getting merge commits in the log. |
Sorry, I happened to use this slick github "resolve merge conflicts" button. |
Merged! Thanks! |
Resize won't work in every conditions, but at least it should work