Skip to content

DOC: cleanup documentation, continuation of nditer PR #9998 #10949

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

Merged
merged 2 commits into from
Apr 27, 2018

Conversation

mattip
Copy link
Member

@mattip mattip commented Apr 21, 2018

After merging PR #9998, careful review picked up some mistakes in documentation

@@ -118,7 +118,7 @@ C API changes
=============

``NpyIter_Close`` has been added and should be called before
``NpyIter_Dealloc`` to resolve possible writeback-enabled arrays.
``NpyIter_Deallocate`` to resolve possible writeback-enabled arrays.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be tempted to add a bullet point to match the other sections without headers in this document.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

with it:
with it:
for x in it:
x[...] = 123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should happen if the outer with continues to use it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will error out that it is closed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems to be consistent with file and contextlib.closing. Can you test that too then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

with it:
for x in it:
x[...] = 123
# make sure we cannot reenter the iterand
# make sure we cannot reenter the closed iterand
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd argue this should be iterator, not iterand - I'd call it.operands the iterands.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@mattip mattip force-pushed the doc-nditer branch 2 times, most recently from 11c71b9 to 2e3e96b Compare April 21, 2018 23:32
@mattip
Copy link
Member Author

mattip commented Apr 21, 2018

squashed after last fix from review

@ahaldane
Copy link
Member

In addition to these fixups, I think I had in mind some other additions to the docs which would be good to put in this PR.... I need to go back and review what those were

@@ -78,6 +78,8 @@ order='C' for C order and order='F' for Fortran order.
...
0 3 1 4 2 5

.. _nditer-context-manager:

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

Copy link
Member

@ahaldane ahaldane Apr 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next few paragraph below this point could use some editing. It uses the term "view" in a possbily misleading way, since the writeback arrays are not actually views, they are temp buffers.

Here's my try going through it:

 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.

 When assigning to the buffers returned by the nditer, also remember that you must 
 perform the assignment using the ellipsis index, as in `x[...] = `, rather than 
 simply reassigning the variable name as in `x = `.
[Note: Consider deleting this last paragaph entirely?]

@ahaldane
Copy link
Member

Also some tweaks in doc/source/reference/c-api.iterator.rst: There is a typo shuold in NpyIter_Deallocate, and maybe in the doc for NpyIter_Close replace the word "must" by "should".

I was originally thinking of adding more wordy description of the need for Npy_Close there, but looking again that file is not very explanatory, it basically just says "look at the numpy code", and I think it's fine to leave it that way.

@ahaldane
Copy link
Member

I guess maybe in the NpyIyer_Close doc, maybe add a sentence about how if you make copies using NpyIter_Copy, you need to Npy_Dealloc all of them, but you should only call NpyIter_Close once since they share buffers.

I think that covers all the doc changes I had in mind.

@mattip mattip force-pushed the doc-nditer branch 2 times, most recently from a5c2458 to c410fdc Compare April 22, 2018 07:19
@mattip
Copy link
Member Author

mattip commented Apr 22, 2018

Also see #10951 which adds more documentation for it.close

@charris
Copy link
Member

charris commented Apr 23, 2018

Needs a rebase.

@mattip
Copy link
Member Author

mattip commented Apr 23, 2018

rebased off master and squashed

``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`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is NpyIter_Copy exposed to python?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, as it.copy(). Should I expand the documentation of it.copy to mention writeback considerations?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won'y np.nditer.__del__ complain if you only call close on the original and not the copy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, the complaint is on the operands, which are shared:

def test_copy():
    a = np.arange(6, dtype='f4')
    au = a.byteswap().newbyteorder()
    it1 = np.nditer(au, [], [['readwrite', 'updateifcopy']],
                        casting='equiv', op_dtypes=[np.dtype('f4')])
    it2 = it1.copy()
    x = it1.operands[0]
    assert x is it2.operands[0]
    assert x.flags.writebackifcopy is True
    it1.close()
    assert x.flags.writebackifcopy is False

commenting the last two lines does emit a warning

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So requiring a context manager wouldn't have worked here anyway?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. The context manager resolves operands on exit:

def test_copy():
    a = np.arange(6, dtype='f4')
    au = a.byteswap().newbyteorder()
    it1 = np.nditer(au, [], [['readwrite', 'updateifcopy']],
                        casting='equiv', op_dtypes=[np.dtype('f4')])
    with it1:
        it2 = it1.copy()
        x = it1.operands[0]
        assert x is it2.operands[0]
        assert x.flags.writebackifcopy is True
    assert x.flags.writebackifcopy is False
    # no warning on it2 deallocate, the operand is resolved via it1

Note that nesting the context managers, it1 is still open even though it2 is closed

def test_copy():
    a = np.arange(6, dtype='f4')
    au = a.byteswap().newbyteorder()
    it1 = np.nditer(au, [], [['readwrite', 'updateifcopy']],
                        casting='equiv', op_dtypes=[np.dtype('f4')])
    with it1:
        with it1.copy() as it2:
            x = it1.operands[0]
            assert x is it2.operands[0]
            assert x.flags.writebackifcopy is True
        assert x.flags.writebackifcopy is False
        y = it1.operands[0] # can still access it1's operand, it is not closed
        assert y is x

Copy link
Member

@eric-wieser eric-wieser Apr 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we allow access to it1.operands and forbid access to it2.operands on that line, when they point to the same object? Either they're both invalid, or neither of them are invalid.

Ie, hasn't the writeback already happened, in which case it.operands is now temporary garbage that won't ever make it back to au?

It seems like preventing access to it.operands after it.close() is called has failed to actually protect against access to a post-writeback array (and so there's no point in imposing the restriction at all)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disallowing new access to operands is a best-effort attempt, and is not fool-proof. x also no longer points to au after writeback resolution. Issue #10956 proposed a change to deal with that. This PR was meant to document the way it works now, with the warts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this PR looks good to me as a doc improvement, but I'm worried that writebackifcopy might have been the wrong design after all. In my opinion, you should have to call close on both iterators for writebacks to be resolved, which would require a reference count.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note added to issue #10958

@eric-wieser
Copy link
Member

@ahaldane, I'm going to leave this for you to merge

@ahaldane
Copy link
Member

All right, looks good. Thanks Matti!

@ahaldane ahaldane merged commit 0a02ea2 into numpy:master Apr 27, 2018
@mattip mattip deleted the doc-nditer branch May 6, 2018 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants