diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 0344ed159b..d948e34c58 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -541,17 +541,22 @@ def copy(self, points=None, bounds=None): raise ValueError('If bounds are specified, points must also be ' 'specified') - new_coord = copy.deepcopy(self) if points is not None: + # We do not perform a deepcopy when we supply new points so as to + # not unncessarily copy the old points. + new_coord = copy.copy(self) + new_coord.attributes = copy.deepcopy(self.attributes) + new_coord.coord_system = copy.deepcopy(self.coord_system) + # Explicitly not using the points property as we don't want the # shape the new points to be constrained by the shape of # self.points new_coord._points = None new_coord.points = points - # Regardless of whether bounds are provided as an argument, new - # points will result in new bounds, discarding those copied from - # self. + # new points will result in new bounds. new_coord.bounds = bounds + else: + new_coord = copy.deepcopy(self) return new_coord diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 8e207f8024..fe24e3eeb3 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -772,6 +772,23 @@ def __init__(self, data, standard_name=None, long_name=None, for cell_measure, dims in cell_measures_and_dims: self.add_cell_measure(cell_measure, dims) + # When True indexing may result in a view onto the original data array, + # to avoid unnecessary copying. + self._share_data = False + + @property + def share_data(self): + """Share cube data when slicing/indexing cube if True.""" + return self._share_data + + @share_data.setter + def share_data(self, value): + # Realise the data if is hasn't already been as sharing lazy data is + # not right now possible or a usecase understood. + if self.has_lazy_data(): + _ = self.data + self._share_data = bool(value) + @property def metadata(self): """ @@ -2177,7 +2194,7 @@ def new_cell_measure_dims(cm_): try: first_slice = next(slice_gen) except StopIteration: - first_slice = None + first_slice = Ellipsis if self.share_data else None if first_slice is not None: data = self._my_data[first_slice] @@ -2189,8 +2206,9 @@ def new_cell_measure_dims(cm_): # We don't want a view of the data, so take a copy of it if it's # not already our own. - if isinstance(data, biggus.Array) or not data.flags['OWNDATA']: - data = copy.deepcopy(data) + if not self.share_data: + if isinstance(data, biggus.Array) or not data.flags['OWNDATA']: + data = copy.deepcopy(data) # We can turn a masked array into a normal array if it's full. if isinstance(data, ma.core.MaskedArray): diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index deee640e47..4140b9a7b6 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -65,6 +65,10 @@ def test_matrix(self): self.assertEqual(type(cube.data), np.ndarray) self.assertArrayEqual(cube.data, data) + def test_default_share_data(self): + cube = Cube(np.arange(1)) + self.assertFalse(cube.share_data) + class Test_extract(tests.IrisTest): def test_scalar_cube_exists(self): @@ -1407,6 +1411,50 @@ def test_remove_cell_measure(self): [[self.b_cell_measure, (0, 1)]]) +class Test_share_data(tests.IrisTest): + def setter_lazy_data(self): + cube = Cube(biggus.NumpyArrayAdapter(np.arange(6).reshape(2, 3))) + cube.share_data = True + self.assertFalse(cube.has_lazy_data()) + self.assertTrue(cube._share_data) + + def setter_realised_data(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube.share_data = True + self.assertFalse(cube.has_lazy_data()) + self.assertTrue(cube._share_data) + + +class Test___getitem__no_share_data(tests.IrisTest): + def test_lazy_array(self): + cube = Cube(biggus.NumpyArrayAdapter(np.arange(6).reshape(2, 3))) + cube2 = cube[1:] + self.assertTrue(cube2.has_lazy_data()) + cube.data + self.assertTrue(cube2.has_lazy_data()) + + def test_ndarray(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube2 = cube[1:] + self.assertIsNot(cube.data.base, cube2.data.base) + + +class Test___getitem__share_data(tests.IrisTest): + def test_lazy_array(self): + cube = Cube(biggus.NumpyArrayAdapter(np.arange(6).reshape(2, 3))) + cube.share_data = True + cube2 = cube[1:] + self.assertFalse(cube.has_lazy_data()) + self.assertFalse(cube2.has_lazy_data()) + self.assertIs(cube.data.base, cube2.data.base) + + def test_ndarray(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube.share_data = True + cube2 = cube[1:] + self.assertIs(cube.data.base, cube2.data.base) + + class Test__getitem_CellMeasure(tests.IrisTest): def setUp(self): cube = Cube(np.arange(6).reshape(2, 3))