Skip to content

Commit

Permalink
DOC: cleanup documentation, continuation of nditer PR numpy#9998
Browse files Browse the repository at this point in the history
  • Loading branch information
mattip committed Apr 23, 2018
1 parent f2888db commit 86608c2
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 37 deletions.
6 changes: 3 additions & 3 deletions doc/release/1.15.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Deprecations
anytime one of the iterator operands is writeable, so that numpy can
manage writeback semantics, or should call ``it.close()``. A
`RuntimeWarning` will be emitted otherwise in these cases. Users of the C-API
should call ``NpyIter_Close`` before ``NpyIter_Dealloc``.
should call ``NpyIter_Close`` before ``NpyIter_Deallocate``.


Future Changes
Expand Down Expand Up @@ -117,8 +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.
* ``NpyIter_Close`` has been added and should be called before
``NpyIter_Deallocate`` to resolve possible writeback-enabled arrays.

New Features
============
Expand Down
39 changes: 20 additions & 19 deletions doc/source/reference/arrays.nditer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,28 @@ order='C' for C order and order='F' for Fortran order.
...
0 3 1 4 2 5

.. _nditer-context-manager:

Modifying Array Values
----------------------

By default, the :class:`nditer` treats the input array as a read-only
object. To modify the array elements, you must specify either read-write
or write-only mode. This is controlled with per-operand flags. The
operands may be created as views into the original data with the
`WRITEBACKIFCOPY` flag. In this case the iterator must either

- be used as a context manager, and the temporary data will be written back
to the original array when the `__exit__` function is called.
- have a call to the iterator's `close` function to ensure the modified data
is written back to the original array.

Regular assignment in Python simply changes a reference in the local or
global variable dictionary instead of modifying an existing variable in
place. This means that simply assigning to `x` will not place the value
into the element of the array, but rather switch `x` from being an array
element reference to being a reference to the value you assigned. To
actually modify the element of the array, `x` should be indexed with
the ellipsis.
By default, the :class:`nditer` treats the input operand as a read-only
object. To be able to modify the array elements, you must specify either
read-write or write-only mode using the `'readwrite'` or `'writeonly'`
per-operand flags.

The nditer will then yield writeable buffer arrays which you may modify. However,
because the nditer must copy this buffer data back to the original array once
iteration is finished, you must signal when the iteration is ended, by one of two
methods. You may either:

- used the nditer as a context manager using the `with` statement, and
the temporary data will be written back when the context is exited.
- call the iterator's `close` method once finished iterating, which will trigger
the write-back.

The nditer can no longer be iterated once either `close` is called or its
context is exited.

.. admonition:: Example

Expand Down Expand Up @@ -186,7 +187,7 @@ construct in order to be more readable.
0 <(0, 0)> 1 <(0, 1)> 2 <(0, 2)> 3 <(1, 0)> 4 <(1, 1)> 5 <(1, 2)>

>>> it = np.nditer(a, flags=['multi_index'], op_flags=['writeonly'])
>>> with it:
>>> with it:
.... while not it.finished:
... it[0] = it.multi_index[1] - it.multi_index[0]
... it.iternext()
Expand Down
16 changes: 11 additions & 5 deletions doc/source/reference/c-api.iterator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,10 @@ Construction and Destruction
the functions will pass back errors through it instead of setting
a Python exception.
:c:func:`NpyIter_Deallocate` must be called for each copy. One call to
:c:func:`NpyIter_Close` is sufficient to trigger writeback resolution for
all copies since they share buffers.
.. c:function:: int NpyIter_RemoveAxis(NpyIter* iter, int axis)``
Removes an axis from iteration. This requires that
Expand Down Expand Up @@ -761,19 +765,21 @@ Construction and Destruction
.. c:function:: int NpyIter_Close(NpyIter* iter)
Resolves any needed writeback resolution. Must be called before
``NpyIter_Deallocate``. After this call it is not safe to use the operands.
Resolves any needed writeback resolution. Should be called before
:c:func::`NpyIter_Deallocate`. After this call it is not safe to use the operands.
When using :c:func:`NpyIter_Copy`, only one call to :c:func:`NpyIter_Close`
is sufficient to resolve any writebacks, since the copies share buffers.
Returns ``0`` or ``-1`` if unsuccessful.
.. c:function:: int NpyIter_Deallocate(NpyIter* iter)
Deallocates the iterator object.
`NpyIter_Close` should be called before this. If not, and if writeback is
needed, it will be performed at this point in order to maintain
:c:func:`NpyIter_Close` should be called before this. If not, and if
writeback is needed, it will be performed at this point in order to maintain
backward-compatibility with older code, and a deprecation warning will be
emmitted. Old code shuold be updated to call `NpyIter_Close` beforehand.
emmitted. Old code should be updated to call `NpyIter_Close` beforehand.
Returns ``NPY_SUCCEED`` or ``NPY_FAIL``.
Expand Down
16 changes: 11 additions & 5 deletions numpy/add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,11 @@ def luf(lamdaexpr, *args, **kwargs):
array([ 0.5, 1.5, 4.5, 9.5, 16.5])
If operand flags `"writeonly"` or `"readwrite"` are used the operands may
be views into the original data with the WRITEBACKIFCOPY flag. In this case
nditer must be used as a context manager. The temporary
data will be written back to the original data when the `` __exit__``
function is called but not before::
be views into the original data with the `WRITEBACKIFCOPY` flag. In this case
nditer must be used as a context manager or the nditer.close
method must be called before using the result. The temporary
data will be written back to the original data when the `__exit__`
function is called but not before:
>>> a = np.arange(6, dtype='i4')[::-2]
>>> with nditer(a, [],
Expand All @@ -405,7 +406,7 @@ def luf(lamdaexpr, *args, **kwargs):
references (like `x` in the example) may or may not share data with
the original data `a`. If writeback semantics were active, i.e. if
`x.base.flags.writebackifcopy` is `True`, then exiting the iterator
will sever the connection between `x` and `a`, writing to `x` will
will sever the connection between `x` and `a`, writing to `x` will
no longer write to `a`. If writeback semantics are not active, then
`x.data` will still point at some part of `a.data`, and writing to
one will affect the other.
Expand Down Expand Up @@ -566,6 +567,11 @@ def luf(lamdaexpr, *args, **kwargs):
Resolve all writeback semantics in writeable operands.
See Also
--------
:ref:`nditer-context-manager`
"""))


Expand Down
2 changes: 1 addition & 1 deletion numpy/core/code_generators/numpy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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 in both setup_common.py and by adding an appropriate
entry to cversion.txt (generate the hash via "python cversions.py".
entry to cversion.txt (generate the hash via "python cversions.py").
When adding a function, make sure to use the next integer not used as an index
(in case you use an existing index or jump, the build will stop and raise an
Expand Down
3 changes: 2 additions & 1 deletion numpy/core/src/multiarray/arrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ NPY_NO_EXPORT int
PyArray_SetUpdateIfCopyBase(PyArrayObject *arr, PyArrayObject *base)
{
int ret;
/* 2017-Nov-10 1.14 */
/* 2017-Nov -10 1.14 (for PyPy only) */
/* 2018-April-21 1.15 (all Python implementations) */
if (DEPRECATE("PyArray_SetUpdateIfCopyBase is deprecated, use "
"PyArray_SetWritebackIfCopyBase instead, and be sure to call "
"PyArray_ResolveWritebackIfCopy before the array is deallocated, "
Expand Down
24 changes: 21 additions & 3 deletions numpy/core/tests/test_nditer.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ def test_iter_nbo_align_contig():
assert_equal(i.operands[0], a)
i.operands[0][:] = 2
assert_equal(au, [2]*6)
i = None # should not raise a DeprecationWarning
del i # should not raise a warning
# Byte order change by requesting NBO
a = np.arange(6, dtype='f4')
au = a.byteswap().newbyteorder()
Expand Down Expand Up @@ -2838,12 +2838,30 @@ def test_writebacks():
it = nditer(au, [],
[['readwrite', 'updateifcopy']],
casting='equiv', op_dtypes=[np.dtype('f4')])
au = None
# reentering works
with it:
with it:
for x in it:
x[...] = 123

it = nditer(au, [],
[['readwrite', 'updateifcopy']],
casting='equiv', op_dtypes=[np.dtype('f4')])
# make sure exiting the inner context manager closes the iterator
with it:
with it:
for x in it:
x[...] = 123
assert_raises(ValueError, getattr, it, 'operands')
# do not crash if original data array is decrefed
it = nditer(au, [],
[['readwrite', 'updateifcopy']],
casting='equiv', op_dtypes=[np.dtype('f4')])
del au
with it:
for x in it:
x[...] = 123
# make sure we cannot reenter the iterand
# make sure we cannot reenter the closed iterator
enter = it.__enter__
assert_raises(ValueError, enter)

Expand Down

0 comments on commit 86608c2

Please sign in to comment.