diff --git a/lib/iris/coords.py b/lib/iris/coords.py
index 47fb832255..163dea52cc 100644
--- a/lib/iris/coords.py
+++ b/lib/iris/coords.py
@@ -523,8 +523,13 @@ 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 unnecessarily copy the old points and bounds.
+ 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
@@ -534,6 +539,8 @@ def copy(self, points=None, bounds=None):
# points will result in new bounds, discarding those copied from
# self.
new_coord.bounds = bounds
+ else:
+ new_coord = copy.deepcopy(self)
return new_coord
@@ -1503,7 +1510,7 @@ def points(self):
@points.setter
def points(self, points):
- points = np.array(points, ndmin=1)
+ points = np.array(points, ndmin=1, copy=False)
# If points are already defined for this coordinate,
if hasattr(self, '_points') and self._points is not None:
# Check that setting these points wouldn't change self.shape
@@ -1539,7 +1546,7 @@ def bounds(self):
def bounds(self, bounds):
if bounds is not None:
# Ensure the bounds are a compatible shape.
- bounds = np.array(bounds, ndmin=2)
+ bounds = np.array(bounds, ndmin=2, copy=False)
if self.shape != bounds.shape[:-1]:
raise ValueError(
"The shape of the bounds array should be "
diff --git a/lib/iris/cube.py b/lib/iris/cube.py
index 7fea4ec398..4ba3ceb0d2 100644
--- a/lib/iris/cube.py
+++ b/lib/iris/cube.py
@@ -2239,25 +2239,18 @@ def new_cell_measure_dims(cm_):
try:
first_slice = next(slice_gen)
except StopIteration:
- first_slice = None
+ first_slice = Ellipsis
if self.has_lazy_data():
cube_data = self._dask_array
else:
cube_data = self._numpy_array
- if first_slice is not None:
- data = cube_data[first_slice]
- else:
- data = copy.deepcopy(cube_data)
+ data = cube_data[first_slice]
for other_slice in slice_gen:
data = data[other_slice]
- # We don't want a view of the data, so take a copy of it if it's
- # not already our own.
- data = copy.deepcopy(data)
-
# We can turn a masked array into a normal array if it's full.
if ma.isMaskedArray(data):
if ma.count_masked(data) == 0:
diff --git a/lib/iris/tests/test_coord_api.py b/lib/iris/tests/test_coord_api.py
index cff8c5664f..613772c657 100644
--- a/lib/iris/tests/test_coord_api.py
+++ b/lib/iris/tests/test_coord_api.py
@@ -662,6 +662,7 @@ def test_guess_bounds(self):
self.assertArrayEqual(coord.bounds, np.array([[-5., 5.], [5., 17.5], [17.5, 27.5], [27.5, 32.5]]))
# if the points are not monotonic, then guess_bounds should fail
+ points = points.copy()
points[2] = 32
coord = iris.coords.AuxCoord.from_coord(coord)
coord.points = points
diff --git a/lib/iris/tests/unit/coords/test_AuxCoord.py b/lib/iris/tests/unit/coords/test_AuxCoord.py
new file mode 100644
index 0000000000..3eccd56a7d
--- /dev/null
+++ b/lib/iris/tests/unit/coords/test_AuxCoord.py
@@ -0,0 +1,121 @@
+# (C) British Crown Copyright 2017, Met Office
+#
+# This file is part of Iris.
+#
+# Iris is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Iris is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Iris. If not, see .
+"""Unit tests for :class:`iris.coords.AuxCoord`."""
+
+from __future__ import (absolute_import, division, print_function)
+from six.moves import (filter, input, map, range, zip) # noqa
+
+# Import iris.tests first so that some things can be initialised before
+# importing anything else.
+import iris.tests as tests
+
+import dask
+import numpy as np
+
+from iris._lazy_data import as_lazy_data
+from iris.coords import AuxCoord
+
+
+class Test___init__(tests.IrisTest):
+ def test_writeable(self):
+ coord = AuxCoord([1, 2], bounds=[[1, 2], [2, 3]])
+ self.assertTrue(coord.points.flags.writeable)
+ self.assertTrue(coord.bounds.flags.writeable)
+
+
+def fetch_base(ndarray):
+ if ndarray.base is not None:
+ return fetch_base(ndarray.base)
+ return ndarray
+
+
+class Test___getitem__(tests.IrisTest):
+ def test_share_data(self):
+ # Ensure that slicing a coordinate behaves like slicing a numpy array
+ # i.e. that the points and bounds are views of the original.
+ original = AuxCoord([1, 2], bounds=[[1, 2], [2, 3]],
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+ sliced_coord = original[:]
+ self.assertIs(fetch_base(sliced_coord._points),
+ fetch_base(original._points))
+ self.assertIs(fetch_base(sliced_coord._bounds),
+ fetch_base(original._bounds))
+ self.assertIsNot(sliced_coord.coord_system, original.coord_system)
+ self.assertIsNot(sliced_coord.attributes, original.attributes)
+
+ def test_lazy_data_realisation(self):
+ # Capture the fact that we realise the data when slicing.
+ points = np.array([1, 2])
+ points = as_lazy_data(points)
+
+ bounds = np.array([[1, 2], [2, 3]])
+ bounds = as_lazy_data(bounds)
+
+ original = AuxCoord(points, bounds=bounds,
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+ sliced_coord = original[:]
+ # Returned coord is realised.
+ self.assertIsInstance(sliced_coord._points, dask.array.core.Array)
+ self.assertIsInstance(sliced_coord._bounds, dask.array.core.Array)
+
+ # Original coord remains unrealised.
+ self.assertIsInstance(points, dask.array.core.Array)
+ self.assertIsInstance(bounds, dask.array.core.Array)
+
+
+class Test_copy(tests.IrisTest):
+ def setUp(self):
+ self.original = AuxCoord([1, 2], bounds=[[1, 2], [2, 3]],
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+
+ def assert_data_no_share(self, coord_copy):
+ self.assertIsNot(fetch_base(coord_copy._points),
+ fetch_base(self.original._points))
+ self.assertIsNot(fetch_base(coord_copy._bounds),
+ fetch_base(self.original._bounds))
+ self.assertIsNot(coord_copy.coord_system, self.original.coord_system)
+ self.assertIsNot(coord_copy.attributes, self.original.attributes)
+
+ def test_existing_points(self):
+ # Ensure that copying a coordinate does not return a view of its
+ # points or bounds.
+ coord_copy = self.original.copy()
+ self.assert_data_no_share(coord_copy)
+
+ def test_existing_points_deepcopy_call(self):
+ # Ensure that the coordinate object itself is deepcopied called.
+ with tests.mock.patch('copy.deepcopy') as mock_copy:
+ self.original.copy()
+ mock_copy.assert_called_once_with(self.original)
+
+ def test_new_points(self):
+ coord_copy = self.original.copy([1, 2], bounds=[[1, 2], [2, 3]])
+ self.assert_data_no_share(coord_copy)
+
+ def test_new_points_shallowcopy_call(self):
+ # Ensure that the coordinate object itself is shallow copied so that
+ # the points and bounds are not unnecessarily copied.
+ with tests.mock.patch('copy.copy') as mock_copy:
+ self.original.copy([1, 2], bounds=[[1, 2], [2, 3]])
+ mock_copy.assert_called_once_with(self.original)
+
+
+if __name__ == '__main__':
+ tests.main()
diff --git a/lib/iris/tests/unit/coords/test_DimCoord.py b/lib/iris/tests/unit/coords/test_DimCoord.py
new file mode 100644
index 0000000000..802d4b6edb
--- /dev/null
+++ b/lib/iris/tests/unit/coords/test_DimCoord.py
@@ -0,0 +1,126 @@
+# (C) British Crown Copyright 2017, Met Office
+#
+# This file is part of Iris.
+#
+# Iris is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Iris is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Iris. If not, see .
+"""Unit tests for :class:`iris.coords.DimCoord`."""
+
+from __future__ import (absolute_import, division, print_function)
+from six.moves import (filter, input, map, range, zip) # noqa
+
+# Import iris.tests first so that some things can be initialised before
+# importing anything else.
+import iris.tests as tests
+
+import copy
+
+import dask
+import numpy as np
+
+from iris._lazy_data import as_lazy_data
+from iris.coords import DimCoord
+
+
+class Test___init__(tests.IrisTest):
+ def test_writeable(self):
+ coord = DimCoord([1, 2], bounds=[[1, 2], [2, 3]])
+ self.assertFalse(coord.points.flags.writeable)
+ self.assertFalse(coord.bounds.flags.writeable)
+
+
+def fetch_base(ndarray):
+ if ndarray.base is not None:
+ return fetch_base(ndarray.base)
+ return ndarray
+
+
+class Test___getitem__(tests.IrisTest):
+ def test_share_data(self):
+ # Ensure that slicing a coordinate behaves like slicing a numpy array
+ # i.e. that the points and bounds are views of the original.
+ original = DimCoord([1, 2], bounds=[[1, 2], [2, 3]],
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+ sliced_coord = original[:]
+ self.assertIs(fetch_base(sliced_coord._points),
+ fetch_base(original._points))
+ self.assertIs(fetch_base(sliced_coord._bounds),
+ fetch_base(original._bounds))
+ self.assertIsNot(sliced_coord.coord_system, original.coord_system)
+ self.assertIsNot(sliced_coord.attributes, original.attributes)
+
+ def test_lazy_data_realisation(self):
+ # Capture the fact that we realise the data when slicing.
+ points = np.array([1, 2])
+ points = as_lazy_data(points)
+
+ bounds = np.array([[1, 2], [2, 3]])
+ bounds = as_lazy_data(bounds)
+
+ original = DimCoord(points, bounds=bounds,
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+ sliced_coord = original[:]
+ # Returned coord is realised.
+ self.assertIsInstance(sliced_coord._points, np.ndarray)
+ self.assertIsInstance(sliced_coord._bounds, np.ndarray)
+
+ # Original coord remains unrealised.
+ self.assertIsInstance(points, dask.array.core.Array)
+ self.assertIsInstance(bounds, dask.array.core.Array)
+
+
+class Test_copy(tests.IrisTest):
+ def setUp(self):
+ self.original = DimCoord([1, 2], bounds=[[1, 2], [2, 3]],
+ attributes={'dummy1': None},
+ coord_system=tests.mock.sentinel.coord_system)
+
+ def assert_data_no_share(self, coord_copy):
+ self.assertIsNot(fetch_base(coord_copy._points),
+ fetch_base(self.original._points))
+ self.assertIsNot(fetch_base(coord_copy._bounds),
+ fetch_base(self.original._bounds))
+ self.assertIsNot(coord_copy.coord_system, self.original.coord_system)
+ self.assertIsNot(coord_copy.attributes, self.original.attributes)
+
+ def test_existing_points(self):
+ # Ensure that copying a coordinate does not return a view of its
+ # points or bounds.
+ coord_copy = self.original.copy()
+ self.assert_data_no_share(coord_copy)
+
+ def test_existing_points_deepcopy_call(self):
+ # Ensure that the coordinate object itself is deepcopied called.
+ cp_orig = copy.deepcopy(self.original)
+ with tests.mock.patch('copy.deepcopy', return_value=cp_orig) as \
+ mock_copy:
+ self.original.copy()
+ mock_copy.assert_called_once_with(self.original)
+
+ def test_new_points(self):
+ coord_copy = self.original.copy([1, 2], bounds=[[1, 2], [2, 3]])
+ self.assert_data_no_share(coord_copy)
+
+ def test_new_points_shallowcopy_call(self):
+ # Ensure that the coordinate object itself is shallow copied so that
+ # the points and bounds are not unnecessarily copied.
+ cp_orig = copy.copy(self.original)
+ with tests.mock.patch('copy.copy', return_value=cp_orig) as mock_copy:
+ self.original.copy([1, 2], bounds=[[1, 2], [2, 3]])
+ mock_copy.assert_called_once_with(self.original)
+
+
+if __name__ == '__main__':
+ tests.main()
diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py
index ae5636e28e..d81058d36b 100644
--- a/lib/iris/tests/unit/cube/test_Cube.py
+++ b/lib/iris/tests/unit/cube/test_Cube.py
@@ -1670,6 +1670,22 @@ def test_remove_cell_measure(self):
[[self.b_cell_measure, (0, 1)]])
+class Test___getitem__lazy(tests.IrisTest):
+ def test_lazy_array(self):
+ data = np.arange(6).reshape(2, 3)
+ data = as_lazy_data(data)
+ cube = Cube(data)
+ 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.assertIs(cube.data.base, cube2.data.base)
+
+
class Test__getitem_CellMeasure(tests.IrisTest):
def setUp(self):
cube = Cube(np.arange(6).reshape(2, 3))