Repository: numpy/numpy

Issue:
nditer.__next__ keeps working after `close` is called
@mattip.

This surprises me a little:
```python
>>> it = np.nditer(np.arange(3))
>>> next(it)
array(0)
>>> it.close()
>>> next(it)
array(1)
>>> next(it)
array(2)
>>> next(it)
StopIteration
```

Compare with
```python
>>> def generator():
...    yield from range(3)

>>> it = generator()
>>> next(it)
0
>>> it.close()
>>> next(it)
StopIteration
```


Discussion:

Resolving patch:
diff --git a/numpy/add_newdocs.py b/numpy/add_newdocs.py
--- a/numpy/add_newdocs.py
+++ b/numpy/add_newdocs.py
@@ -257,6 +257,7 @@
     dtypes : tuple of dtype(s)
         The data types of the values provided in `value`. This may be
         different from the operand data types if buffering is enabled.
+        Valid only before the iterator is closed.
     finished : bool
         Whether the iteration over the operands is finished or not.
     has_delayed_bufalloc : bool
@@ -282,7 +283,8 @@
         Size of the iterator.
     itviews
         Structured view(s) of `operands` in memory, matching the reordered
-        and optimized iterator access pattern.
+        and optimized iterator access pattern. Valid only before the iterator
+        is closed.
     multi_index
         When the "multi_index" flag was used, this property
         provides access to the index. Raises a ValueError if accessed
@@ -292,7 +294,8 @@
     nop : int
         The number of iterator operands.
     operands : tuple of operand(s)
-        The array(s) to be iterated over.
+        The array(s) to be iterated over. Valid only before the iterator is
+        closed.
     shape : tuple of ints
         Shape tuple, the shape of the iterator.
     value
@@ -331,12 +334,12 @@ def iter_add(x, y, out=None):
 
             it = np.nditer([x, y, out], [],
                         [['readonly'], ['readonly'], ['writeonly','allocate']])
+            with it:
+                while not it.finished:
+                    addop(it[0], it[1], out=it[2])
+                    it.iternext()
 
-            while not it.finished:
-                addop(it[0], it[1], out=it[2])
-                it.iternext()
-
-            return it.operands[2]
+                return it.operands[2]
 
     Here is an example outer product function::
 
@@ -351,7 +354,7 @@ def outer_it(x, y, out=None):
             with it:
                 for (a, b, c) in it:
                     mulop(a, b, out=c)
-            return it.operands[2]
+                return it.operands[2]
 
         >>> a = np.arange(2)+1
         >>> b = np.arange(3)+1
@@ -374,7 +377,7 @@ def luf(lamdaexpr, *args, **kwargs):
             while not it.finished:
                 it[0] = lamdaexpr(*it[1:])
                 it.iternext()
-            return it.operands[0]
+                return it.operands[0]
 
         >>> a = np.arange(5)
         >>> b = np.ones(5)
@@ -430,6 +433,13 @@ def luf(lamdaexpr, *args, **kwargs):
 
     """))
 
+add_newdoc('numpy.core', 'nditer', ('operands',
+    """
+    operands[`Slice`]
+
+    The array(s) to be iterated over. Valid only before the iterator is closed.
+    """))
+
 add_newdoc('numpy.core', 'nditer', ('debug_print',
     """
     debug_print()


Test patch:
diff --git a/numpy/core/tests/test_nditer.py b/numpy/core/tests/test_nditer.py
--- a/numpy/core/tests/test_nditer.py
+++ b/numpy/core/tests/test_nditer.py
@@ -2847,7 +2847,7 @@ def test_writebacks():
     enter = it.__enter__
     assert_raises(ValueError, enter)
 
-def test_close():
+def test_close_equivalent():
     ''' using a context amanger and using nditer.close are equivalent
     '''
     def add_close(x, y, out=None):
@@ -2856,8 +2856,10 @@ def add_close(x, y, out=None):
                     [['readonly'], ['readonly'], ['writeonly','allocate']])
         for (a, b, c) in it:
             addop(a, b, out=c)
+        ret = it.operands[2]
         it.close()
-        return it.operands[2]
+        return ret
+
     def add_context(x, y, out=None):
         addop = np.add
         it = np.nditer([x, y, out], [],
@@ -2871,6 +2873,13 @@ def add_context(x, y, out=None):
     z = add_context(range(5), range(5))
     assert_equal(z, range(0, 10, 2))
 
+def test_close_raises():
+    it = np.nditer(np.arange(3))
+    assert_equal (next(it), 0)
+    it.close()
+    assert_raises(StopIteration, next, it)
+    assert_raises(ValueError, getattr, it, 'operands')
+
 def test_warn_noclose():
     a = np.arange(6, dtype='f4')
     au = a.byteswap().newbyteorder()