From 3a816a4db9b498eb64eb837fdcca0fa8ddbe063e Mon Sep 17 00:00:00 2001 From: Allan Haldane Date: Sat, 17 Oct 2015 14:00:36 -0400 Subject: [PATCH] BUG: recarrays viewed as subarrays don't convert to np.record type Record array views were updated in #5943 to return np.record dtype where possible, but forgot about the case of sub-arrays. That's fixed here, so accessing subarray fields by attribute or index works sensibly, as well as viewing a record array as a subarray dtype, and printing subarrays. This also happens to fix #6459, since it affects the same lines. Fixes #6497 #6459 --- numpy/core/records.py | 30 +++++++++++++++++++----------- numpy/core/tests/test_records.py | 23 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/numpy/core/records.py b/numpy/core/records.py index 4a99553..4ce3fe9 100644 --- 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 diff --git a/numpy/core/tests/test_records.py b/numpy/core/tests/test_records.py index 7a18f29..290bc4f 100644 --- 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),