-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
ENH: Nditer as context manager #9998
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
Conversation
a68c7ef
to
12d6caf
Compare
Is it possible to create a simple custom context manager for
It may also be easier do this in python instead of trying to modify (incidentally, |
IHMO just reimplement nested for python 3 and return that:
|
Rare case where Python 2 has a better API. |
numpy/core/src/multiarray/mapping.c
Outdated
@@ -2143,6 +2144,7 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op) | |||
return -1; | |||
|
|||
success: | |||
PyArray_ResolveWritebackIfCopy(view); |
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.
Is this related to np.nditer
? (edit: I see, separate commit)
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.
Sorry if this is a naive question, I'm just coming back to this:
Why is this line needed? Doesn't the context manager exit
take care of any needed writebacks? Furthermore, let's say that within the context manager the user creates a view of the array they got from nditer, and writes to the view (Thus calling Writeback
) and then the context manager exits, doesn't that double-call the writeback code?
This PR segfaults on the following for me:
a = np.arange(24, dtype='f8').reshape(2, 3, 4).T
with np.nditer(a, [],[['readwrite', 'updateifcopy']],casting='same_kind',op_dtypes=[np.dtype('f4')]) as i:
i.operands[0][...] = 3
i.operands[0][...] = 14
which I suspect is related to my comment, but I haven't checked carefully.
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.
good catch. Added your test and fixed in c6a1a2c6d09. The context manager does indeed clean up, but this function can possibly allocate a new ndarray with writebackifcopy
semantics that need to be cleaned up. The fix was to call the resolve function iff a new view
is created
/* iter must used as a context manager if writebackifcopy semantics used */ | ||
#define ITR_INSIDE 1<<0 | ||
#define ITR_HAS_WRITEBACKIFCOPY 1<<1 | ||
#define ITR_EXITED 1<<2 |
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.
Why not an enum?
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.
Or even just a char
for each flag - we don't care about efficiency here.
Perhaps
char has_writebackifcopy;
enum {
CONTEXT_BEFORE,
CONTEXT_INSIDE,
CONTEXT_AFTER
} context_state;
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.
fixed in 9a45b3d055
@@ -2331,6 +2381,39 @@ npyiter_ass_subscript(NewNpyArrayIterObject *self, PyObject *op, | |||
return -1; | |||
} | |||
|
|||
static PyObject * | |||
npyiter_self(NewNpyArrayIterObject *self) |
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.
Would be better as npyiter_enter
since this also sets flags.
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.
fixed in 9a45b3d055
PyErr_SetString(PyExc_ValueError, "operation on non-initialized iterator"); | ||
return NULL; | ||
} | ||
self->managed &= ITR_INSIDE; |
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.
Should this be |=
?
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.
refactored in 9a45b3d055
if (PyArray_ResolveWritebackIfCopy(*object) < 0) | ||
return NULL; | ||
} | ||
self->managed = ITR_EXITED; |
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.
Why does this clear ITR_HAS_WRITEBACKIFCOPY
?
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.
IIUC, PyArray_ResolveWritebackIfCopy
is called, so there's no need to worry about writing back.
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.
So what does the following do ?
it = np.nditer(...)
with it:
next(it)
with it:
# do stuff
It seems to me that we'll forget that we were supposed to be in a context manager the second time around
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.
That's currently undefined behavior.
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.
Obviously that should be fixed by moving the constructor logic into __enter__
.
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.
IMO calling __enter__
the second time should raise an exception since writebackifcopy
resolution would have already been triggered. Is it common to try to re-enter a context manager? In my limited experience I have not seen code like that
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.
Raising an exception sounds reasonable to me - as long as it doesn't fail silently
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.
now raises an error, tested in 9a45b3d055
object = NpyIter_GetOperandArray(iter); | ||
for(iop = 0; iop < nop; ++iop, ++object) { | ||
if (PyArray_ResolveWritebackIfCopy(*object) < 0) | ||
return NULL; |
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.
Braces and/or indentation needed here
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.
fixed in 9a45b3d055
} | ||
|
||
static PyObject * | ||
npyiter_exit(PyObject *f, PyObject *args) |
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.
Why not declare f
as NewNpyArrayIterObject
like you do in npy_iter_self
?
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.
fixed in 9a45b3d055
PyErr_SetString(PyExc_ValueError, | ||
"Iterator has exited its context"); | ||
return NULL; | ||
} |
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.
Why is this desirable? How do operands
interact with WRITEBACK_IF_COPY? Do they contain the temporary, or the target of the writeback?
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.
IIUC, you must call PyArray_ResolveWritebackIfCopy
only once. I think operands contain the temporary copy, they operate on it, and then when the context has exited, the results are written back. Obviously they cannot be written back if the context has already exited.
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.
Not only that, but once they have been written back, the temporary scratch buffer is removed. Now the whole reason for the writeback semantics have been undone, for instance the operand dtype conflicts with the underlying base array's dtype. Reading from the operand will most likely retrieve the wrong value, or writing to it will corrupt the data.
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.
Would be ideal to recreate the scratch buffer in every __enter__
. But fixing the deprecation is a higher priority IMHO.
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.
So to be clear, operands
contains the temporary scratch buffer, not the original array?
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 operands contain the temporary copy
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.
operands are ndarrays
that have the original array as a base
attribute. Since they have the WRITEBACKIFCOPY flag, they hold another scratch buffer until PyArray_ResolveWritebackIfCopy
is called
numpy/core/tests/test_nditer.py
Outdated
assert_(not it.iterationneedsapi) | ||
assert_(sys.getrefcount(a) > rc_a) | ||
assert_(sys.getrefcount(dt) > rc_dt) | ||
# 'it' is still alive in this scope, dealloc |
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.
del it
might be clearer here
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.
fixed in 9a45b3d055
numpy/core/tests/test_nditer.py
Outdated
@@ -2284,7 +2284,8 @@ def test_iter_nested_iters_dtype_copy(): | |||
assert_equal(vals, [[0, 1, 2], [3, 4, 5]]) | |||
vals = None | |||
|
|||
# updateifcopy | |||
# writebackifcopy | |||
# XXX ugly - is there a better way? np.nested_iter returns a tuple |
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.
Why not with i, j
?
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.
That's one possibility. I think people were looking for a one-line solution and Python 3 may require a two-line solution.
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 don't have a good enough understanding of nested_iters
to know what makes sense here. Is it documented anywhere? Do the nditer
s it returns depend on each other? If so, isn't it problematic to construct the inner iter before calling __enter__
on the outer one? (which is exactly the reason that python 3 removed contextlib.nested
)
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.
It is part of the public API, but it isn't documented.
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.
It would be nice if it was at least internally documented. Right now, I have very little idea of what it does.
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 would prefer to resolve the nested_iter
issue in a separate pull request. I have filed issue #10009 a a reminder to do so. AFAICT the c-api function NpyIter_NestedIters is not part of the public API, so we could fix this on the python level only
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.
heh, in the end it is enough to do what @eric-wieser suggested in the first comment, simple and clear. I left the issue open since the function is not documented
doc/release/1.14.0-notes.rst
Outdated
``dealloc`` call, which let to strange ``i = None`` code after iteration to | ||
magically write the temporary data back to the original source. Now nditers | ||
can be used as context managers, and when writebackoncopy semantics are | ||
triggered they *must* be used as context managers. |
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.
Unfortunately, AFAICT there are circumstances where nditer
can use UPDATEIFCOPY even if "updateifcopy"
isn't passed, like if you have a writable array that overlaps with another array.
Suggested text:
Under certain conditions, nditer must be used in a context manager
-----------------------------------------------
When using an nditer with the `"writeonly"` or `"readwrite"` flags, there are some
circumstances where nditer doesn't actually give you a view onto the writable
array. Instead, it gives you a copy, and if you make changes to the copy, nditer later
writes those changes back into your actual array. Currently, this writeback depends
on when the array objects are garbage collected, which makes this API error-prone
on CPython and entirely broken on PyPy. Therefore, `nditer` can now be used
as a context manager (`with np.nditer(...) as it: ...`), and in the future this will
be required whenever using `nditer` with writable arrays.
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.
Note also that I'm suggesting here that we actually make the criterion for issuing deprecation warnings (now) and raising an error (later) be no context manager + writable arrays, since that's so much easier to explain to users than the actual edge cases. (And in particular if you have writable arrays then it's easy to get into a situation where in testing you never hit the UPDATEIFCOPY path, but then in production...)
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 tried to couch the wording in a way that did not limit it to the single flag. Requiring any writable nditer to be used inside a context manager pretty much means any nditer must be used that way. Is a change that deep acceptable/desired?
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.
The problem is that anyone who uses writable nditer might need this depending on details of the input arrays memory layout (in particular whether they overlap). I think forcing everyone to change at once with a nice warning message is better than forcing people to change piecemeal as they accidentally hit the edge case one by one.
Fortunately almost no-one uses nditer :-)
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 went through all the hits for nditer
on searchcode.com, and only found ~15 projects, of which only ~5 use writeonly/readwrite. I'm sure there are others not in their corpus, but we're still only talking about probably ~dozens of pieces of code worldwide that need updating.
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.
fixed in 7eb8aed72
doc/release/1.14.0-notes.rst
Outdated
|
||
``DeprecationWarning`` warnings will be issued on use of | ||
``UPDATEIFCOPY`` or if writeback resolution has not | ||
occurred before calling ``array_dealloc``. |
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.
If array_dealloc
sees WRITEBACKIFCOPY
then that's just a bug, right? Shouldn't we issue a RuntimeWarning
or something? (Really it ought to raise an exception, but since you can't do that from array_dealloc
a noisy warning is a reasonable alternative.)
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.
fixed in 7e5c2a7e6, warning message adjusted to indicate the root cause could be nditer
# define MSG_FromString PyString_FromString | ||
#else | ||
# define MSG_FromString PyUnicode_FromString | ||
#endif |
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.
Isn't this just our PyUString_FromString
macro?
numpy/core/src/multiarray/mapping.c
Outdated
@@ -2134,6 +2134,7 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op) | |||
|
|||
/* Clean up temporary variables and indices */ | |||
fail: | |||
PyArray_DiscardWritebackIfCopy(view); |
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 assuming view
can be NULL
here from the XDECREF
below. Is this save if view
is NULL
?
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.
PyArray_DiscardWritebackIfCopy(arr)
does check if arr
is NULL
for (iop = 0; iop < nop; ++iop) { | ||
if (op_flags[iop] & NPY_ITER_USE_CONTEXT_MANAGER) | ||
self->has_writebackifcopy = 1; | ||
op_flags[iop] &= ~NPY_ITER_USE_CONTEXT_MANAGER; |
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.
Why do you clear this?
@@ -1065,6 +1088,12 @@ NpyIter_NestedIters(PyObject *NPY_UNUSED(self), | |||
Py_DECREF(ret); | |||
goto fail; | |||
} | |||
if ((iter->has_writebackifcopy != 0) && | |||
(iter->managed != CONTEXT_INSIDE)) { |
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.
This second condition is always true
, right, because there's no way to be inside the context already here?
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.
gaah, this is a bug. I need to check the operands and set iter->has_writebackifcopy
appropriately
|
||
if ((self->has_writebackifcopy != 0) && | ||
(self->managed != CONTEXT_INSIDE)) { | ||
/* 10-Nov-2017 v1.14 */ |
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.
Nit: elsewhere we write this 2017-11-10
or 2017-Nov-10
nop = NpyIter_GetNOp(iter); | ||
object = NpyIter_GetOperandArray(iter); | ||
for(iop = 0; iop < nop; ++iop, ++object) { | ||
if (PyArray_ResolveWritebackIfCopy(*object) < 0) { |
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.
Should this call Resolve
even if an error occurs? (args
doesn't contain Py_None
s)
Or would it be better to Discard?
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.
correct, I did not properly handle the error condition. TODO
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.
fixed in 0c3340936f3
4299e5e
to
7eb8aed
Compare
One overall comment - it's very rare to have something in python that is required to be a context manager - for instance, the Perhaps we should add
|
I have no strong opinion on |
1e152b4
to
38f7a34
Compare
38f7a34
to
c26d273
Compare
All right. I'm going to do one last pass through the code soon, and then I am aiming to merge sometime in the next few days. |
if (op_flags[iop] & NPY_ITER_READWRITE) { | ||
self->needs_context_manager = 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.
extra blank line here
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, all lines related to needs_context_manager
can be removed now.
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.
fixed
All right, I did my pass. Just a few nits, and removal of After you fix those last few, I will merge. |
94730b0
to
fa9a741
Compare
All right! Here goes.... Thank you so much for bearing with all the reviews, I think it is quite a nice PR now. |
@@ -5,7 +5,8 @@ | |||
|
|||
Whenever you change one index, you break the ABI (and the ABI version number | |||
should be incremented). Whenever you add an item to one of the dict, the API | |||
needs to be updated. | |||
needs to be updated in both setup_common.py and by adding an appropriate | |||
entry to cversion.txt (generate the hash via "python cversions.py". |
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.
nit: missing paren
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.
What is "best practice" when fixing a merged PR, a new PR or modifying this one again?
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 can't modify an existing PR once it's merged.
What I normally do is keep working in the same branch, but open a new PR
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.
fixed (in doc-nditer PR)
""" | ||
close() | ||
|
||
Resolve all writeback semantics in writeable operands. |
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.
Perhaps reference the with
statement here.
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.
fixed (in doc-nditer PR)
@@ -93,6 +117,8 @@ using the old API. | |||
C API changes | |||
============= | |||
|
|||
``NpyIter_Close`` has been added and should be called before | |||
``NpyIter_Dealloc`` to resolve possible writeback-enabled arrays. |
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.
Should this be NpyIter_Deallocate
?
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.
fixed (in doc-nditer PR)
assert_equal(au, [2]*6) | ||
|
||
i = None # should not raise a DeprecationWarning |
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.
nit: PEP8 wants two spaces before the #. Also, del i
might be clearer.
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.
fixed (in doc-nditer PR)
#ifdef DEPRECATE_UPDATEIFCOPY | ||
/* TODO: enable this once a solution for UPDATEIFCOPY | ||
* and nditer are resolved, also pending the fix for GH7054 | ||
*/ | ||
/* 2017-Nov-10 1.14 */ |
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 this needs an additional comment for the non-pypy deprecation
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.
What would you suggest? Leaving the "GH7054" issue reference as a hint?
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.
/* 2017-11-10 1.14 (only in pypy) */
/* 2018-04-21 1.15 (all python implementations) */
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.
fixed
struct NewNpyArrayIterObject_tag { | ||
PyObject_HEAD | ||
/* The iterator */ | ||
NpyIter *iter; | ||
/* Flag indicating iteration started/stopped */ | ||
char started, finished; | ||
/* iter must used as a context manager if writebackifcopy semantics used */ | ||
char managed; |
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.
The comment above this line is no longer correct, right?
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.
fixed
PyErr_SetString(PyExc_ValueError, "cannot reuse iterator after exit"); | ||
return NULL; | ||
} | ||
self->managed = CONTEXT_INSIDE; |
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.
Should probably also check that you're not re-entering the iterator
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.
Either that, or allow re-entrant with
s, but only cleanup on the outermost one.
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.
Reentrant is OK (test added), multiple cleanups are harmless. Once the cleanup is finished, we set self->managed = CONTEXT_EXITED
so reentering a closed nditer fails (there is a test for that in test_nditer.py
, line 2847
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 guess the question is, is this the behavior we expect?
with it:
with it:
do_stuff_with_it() # ok
do_stuff_with(it) # error: already closed
It does seem to match how contextlib.closing
works, so I suppose is consistent.
* Returns 0 on success, -1 on failure | ||
*/ | ||
NPY_NO_EXPORT int | ||
NpyIter_Close(NpyIter *iter) |
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.
Is it safe to call this multiple times?
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.
yes
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.
the first call will resolve all writebacks, so if there was a writeback scratch buffer now the iterator operand's base
attribute will be None
and the operand's data
will be independent from the original writeable array data
. Subsequent calls will no do anything
@@ -1473,6 +1489,12 @@ static PyObject *npyiter_itviews_get(NewNpyArrayIterObject *self) | |||
return NULL; | |||
} | |||
|
|||
if (self->managed == CONTEXT_EXITED) { | |||
PyErr_SetString(PyExc_ValueError, | |||
"Iterator is closed"); |
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.
Shouldn't this happen after .close()
is called, not just after __exit__
is called?
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.
yes, see new PR for issue #10950
DOC: cleanup documentation, continuation of nditer PR #9998
Resolves issue #9714 by enabling use of nditer as a context manager. Code like this:
now becomes
No more need to do the
it = None
assignment to write the data back to the original array. Tests were adjusted. In addition, DeprecationWarning will be raised if a nditer should be using this code pattern and is not, or if the nditerit
is used outside the context manager.This also allows completing the confusing part of pull request #9639, and now any call to
PyArray_SetUpdateIfCopyBase
will emit a DeprecationWarning.I am not really happy with the way this affects
nested_iters
, since it returns a tuple there is no convienient way to handle the context manager there, that is why the test tweak is in a seperate commit.