Repository: numpy/numpy

Issue:
error when making view of a recarray of an aligned record dtype
A `TypeError: view would access data parent array doesn't own` is raised when a view is created from a recarray and its dtype is an aligned record.  The following code demonstrate this error on py3.4 numpy1.10:

``` python
import numpy as np

fields = [('a', np.float32), ('b', np.int8)]
nonaligned_dtype = np.dtype(fields)
print(nonaligned_dtype, nonaligned_dtype.type)

aligned_dtype = np.dtype(fields, align=True)
print(aligned_dtype, aligned_dtype.type)

nonaligned_array = np.recarray(1, dtype=nonaligned_dtype)
aligned_array = np.recarray(1, dtype=aligned_dtype)

print(nonaligned_array.dtype)
print(aligned_array.dtype)

print(nonaligned_array[:])  # nonaligned record works
print(aligned_array[:])  # <--- raises view would access data parent array doesn't own
```

The cause of the error is likely due to the loss of the `aligned` attribute at https://github.com/numpy/numpy/blob/b65cdbf41bdf11aa4732983555a32043f9abec13/numpy/core/records.py#L502. The itemsize of the `aligned_dtype` is 8 while the `nonaligned_dtype` has itemsize 5.  

numpy 1.10.1 reduce operation on recarrays
Fails.



Discussion:
Thanks for the report, it's good to know this problem comes up in the real world. 

Yes, that line should really read

```
return obj.view(dtype=(self.dtype.type, obj.dtype))
```

This is fixed in #6053. If that PR does not get into 1.11, we should have a separate PR with just that fix.



Resolving patch:
diff --git a/numpy/core/records.py b/numpy/core/records.py
--- a/numpy/core/records.py
+++ b/numpy/core/records.py
@@ -448,12 +448,14 @@ def __getattribute__(self, attr):
 
         # At this point obj will always be a recarray, since (see
         # PyArray_GetField) the type of obj is inherited. Next, if obj.dtype is
-        # non-structured, convert it to an ndarray. If obj is structured leave
-        # it as a recarray, but make sure to convert to the same dtype.type (eg
-        # to preserve numpy.record type if present), since nested structured
-        # fields do not inherit type.
+        # non-structured, convert it to an ndarray. Then if obj is structured
+        # with void type convert it to the same dtype.type (eg to preserve
+        # numpy.record type if present), since nested structured fields do not
+        # inherit type. Don't do this for non-void structures though.
         if obj.dtype.fields:
-            return obj.view(dtype=(self.dtype.type, obj.dtype.fields))
+            if issubclass(obj.dtype.type, nt.void):
+                return obj.view(dtype=(self.dtype.type, obj.dtype))
+            return obj
         else:
             return obj.view(ndarray)
 
@@ -463,8 +465,9 @@ def __getattribute__(self, attr):
     # Thus, you can't create attributes on-the-fly that are field names.
     def __setattr__(self, attr, val):
 
-        # Automatically convert (void) dtypes to records.
-        if attr == 'dtype' and issubclass(val.type, nt.void):
+        # Automatically convert (void) structured types to records
+        # (but not non-void structures, subarrays, or non-structured voids)
+        if attr == 'dtype' and issubclass(val.type, nt.void) and val.fields:
             val = sb.dtype((record, val))
 
         newattr = attr not in self.__dict__
@@ -499,7 +502,9 @@ def __getitem__(self, indx):
         # we might also be returning a single element
         if isinstance(obj, ndarray):
             if obj.dtype.fields:
-                return obj.view(dtype=(self.dtype.type, obj.dtype.fields))
+                if issubclass(obj.dtype.type, nt.void):
+                    return obj.view(dtype=(self.dtype.type, obj.dtype))
+                return obj
             else:
                 return obj.view(type=ndarray)
         else:
@@ -519,11 +524,14 @@ def __repr__(self):
             # If this is a full record array (has numpy.record dtype),
             # or if it has a scalar (non-void) dtype with no records,
             # represent it using the rec.array function. Since rec.array
-            # converts dtype to a numpy.record for us, use only dtype.descr,
-            # not repr(dtype).
+            # converts dtype to a numpy.record for us, convert back
+            # to non-record before printing
+            plain_dtype = self.dtype
+            if plain_dtype.type is record:
+                plain_dtype = sb.dtype((nt.void, plain_dtype))
             lf = '\n'+' '*len("rec.array(")
             return ('rec.array(%s, %sdtype=%s)' %
-                          (lst, lf, repr(self.dtype.descr)))
+                          (lst, lf, plain_dtype))
         else:
             # otherwise represent it using np.array plus a view
             # This should only happen if the user is playing


Test patch:
diff --git a/numpy/core/tests/test_records.py b/numpy/core/tests/test_records.py
--- a/numpy/core/tests/test_records.py
+++ b/numpy/core/tests/test_records.py
@@ -121,6 +121,23 @@ def test_recarray_views(self):
         assert_equal(type(rv), np.recarray)
         assert_equal(rv.dtype.type, np.record)
 
+        # check that accessing nested structures keep record type, but
+        # not for subarrays, non-void structures, non-structured voids
+        test_dtype = [('a', 'f4,f4'), ('b', 'V8'), ('c', ('f4',2)),
+                      ('d', ('i8', 'i4,i4'))]
+        r = np.rec.array([((1,1), b'11111111', [1,1], 1),
+                          ((1,1), b'11111111', [1,1], 1)], dtype=test_dtype)
+        assert_equal(r.a.dtype.type, np.record)
+        assert_equal(r.b.dtype.type, np.void)
+        assert_equal(r.c.dtype.type, np.float32)
+        assert_equal(r.d.dtype.type, np.int64)
+        # check the same, but for views
+        r = np.rec.array(np.ones(4, dtype='i4,i4'))
+        assert_equal(r.view('f4,f4').dtype.type, np.record)
+        assert_equal(r.view(('i4',2)).dtype.type, np.int32)
+        assert_equal(r.view('V8').dtype.type, np.void)
+        assert_equal(r.view(('i8', 'i4,i4')).dtype.type, np.int64)
+
         #check that we can undo the view
         arrs = [np.ones(4, dtype='f4,i4'), np.ones(4, dtype='f8')]
         for arr in arrs:
@@ -135,6 +152,12 @@ def test_recarray_repr(self):
         a = np.array(np.ones(4, dtype='f8'))
         assert_(repr(np.rec.array(a)).startswith('rec.array'))
 
+        # check that the 'np.record' part of the dtype isn't shown
+        a = np.rec.array(np.ones(3, dtype='i4,i4'))
+        assert_equal(repr(a).find('numpy.record'), -1)
+        a = np.rec.array(np.ones(3, dtype='i4'))
+        assert_(repr(a).find('dtype=int32') != -1)
+
     def test_recarray_from_names(self):
         ra = np.rec.array([
             (1, 'abc', 3.7000002861022949, 0),