From 276e447be91458fc0b023788516a5bbef6ad204a Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Mon, 9 Jan 2017 15:01:36 +0000 Subject: [PATCH 1/4] ENH: Remove deepcopies when slicing cubes and copying coords - When indexing a cube when the data is reaslised (not lazy), a view of the original array is returned where possible (subject to the rules when slicing in numpy). - When indexing a cube when the data is not reaslised (lazy), realising the data on one will still not realise the data on the other. - Existing behaviour is that slicing coordinates returns views of the original points and bounds (where possible). This was likely chosen behaviour on the basis that DimCoords at least are not writeable. This is not the same however for Auxiliary coordinates and likely raises the likely case for this being a bug (i.e. one can modify AuxCoord object points and bounds). - DimCoord slicing will now return views of its data like AuxCoords. DimCoords will continue to realise data unlike AuxCoords due to the validation necessary for being monotonically increasing. --- .../deprecate_2016-Dec-08_share_data.txt | 3 + lib/iris/__init__.py | 13 +- lib/iris/coords.py | 15 ++- lib/iris/cube.py | 30 ++++- lib/iris/tests/test_coord_api.py | 32 +++-- .../unit/analysis/cartography/test_project.py | 8 +- lib/iris/tests/unit/coords/test_AuxCoord.py | 120 +++++++++++++++++ lib/iris/tests/unit/coords/test_DimCoord.py | 125 ++++++++++++++++++ lib/iris/tests/unit/cube/test_Cube.py | 50 ++++++- lib/iris/tests/unit/test_Future.py | 8 +- 10 files changed, 375 insertions(+), 29 deletions(-) create mode 100644 docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt create mode 100644 lib/iris/tests/unit/coords/test_AuxCoord.py create mode 100644 lib/iris/tests/unit/coords/test_DimCoord.py diff --git a/docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt b/docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt new file mode 100644 index 0000000000..e35dabf23a --- /dev/null +++ b/docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt @@ -0,0 +1,3 @@ +* Deprecated the data-copying behaviour of Cube indexing. + The `share_data` attribute of `iris.FUTURE` can be used to switch to + the new data-sharing behaviour See :class:`iris.Future`. diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 3019a0adcb..509b988731 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -145,7 +145,7 @@ class Future(threading.local): def __init__(self, cell_datetime_objects=False, netcdf_promote=False, strict_grib_load=False, netcdf_no_unlimited=False, - clip_latitudes=False): + clip_latitudes=False, share_data=False): """ A container for run-time options controls. @@ -200,20 +200,27 @@ def __init__(self, cell_datetime_objects=False, netcdf_promote=False, :meth:`iris.coords.Coord.guess_bounds()` method limits the guessed bounds to [-90, 90] for latitudes. + The option `share_data` controls whether indexing a Cube returns + a Cube whose data is a view onto the original Cube's data, as + opposed to a independent copy of the relevant data. Conversely, when + share_data=False (currently the default), an indexed partial cube + always contains fully independent, copied data arrays. + """ self.__dict__['cell_datetime_objects'] = cell_datetime_objects self.__dict__['netcdf_promote'] = netcdf_promote self.__dict__['strict_grib_load'] = strict_grib_load self.__dict__['netcdf_no_unlimited'] = netcdf_no_unlimited self.__dict__['clip_latitudes'] = clip_latitudes + self.__dict__['share_data'] = share_data def __repr__(self): msg = ('Future(cell_datetime_objects={}, netcdf_promote={}, ' 'strict_grib_load={}, netcdf_no_unlimited={}, ' - 'clip_latitudes={})') + 'clip_latitudes={}, share_data={})') return msg.format(self.cell_datetime_objects, self.netcdf_promote, self.strict_grib_load, self.netcdf_no_unlimited, - self.clip_latitudes) + self.clip_latitudes, self.share_data) deprecated_options = { 'strict_grib_load': ('This is because "iris.fileformats.grib" is now ' diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 20d4058b6a..701515858a 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2010 - 2016, Met Office +# (C) British Crown Copyright 2010 - 2017, Met Office # # This file is part of Iris. # @@ -522,8 +522,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. + 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 @@ -533,6 +538,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 @@ -1502,7 +1509,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 @@ -1538,7 +1545,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 79fb074ea0..756f9745d3 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2010 - 2016, Met Office +# (C) British Crown Copyright 2010 - 2017, Met Office # # This file is part of Iris. # @@ -2153,6 +2153,14 @@ def __getitem__(self, keys): requested must be applicable directly to the cube.data attribute. All metadata will be subsequently indexed appropriately. + .. deprecated:: 1.12 + In future, the `data` attribute of the indexing result may be a + view onto the original data array, to avoid unnecessary copying. + For the present, however, indexing always produces a full + independent copy. The `share_data` attribute of `iris.FUTURE` + should be set to True to enable this new data-sharing behaviour + (if not, a deprecation warning will be issued). + """ # turn the keys into a full slice spec (all dims) full_slice = iris.util._build_full_slice_given_keys(keys, @@ -2177,7 +2185,7 @@ def new_cell_measure_dims(cm_): try: first_slice = next(slice_gen) except StopIteration: - first_slice = None + first_slice = Ellipsis if iris.FUTURE.share_data else None if first_slice is not None: data = self._my_data[first_slice] @@ -2187,10 +2195,18 @@ def new_cell_measure_dims(cm_): 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. - if isinstance(data, biggus.Array) or not data.flags['OWNDATA']: - data = copy.deepcopy(data) + if not iris.FUTURE.share_data: + # We don't want a view of the data, so take a copy of it if it's + # not already our own. + msg = ('The `data` attribute of the indexing result is currently ' + 'a copy of the original data array. This behaviour was ' + 'deprecated in v1.12.0 and will be replaced with views ' + 'onto the original data array where possible. Set ' + 'iris.FUTURE.share_data=True to switch to the new ' + 'behaviour.') + warn_deprecated(msg) + 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): @@ -3108,7 +3124,7 @@ def add_history(self, string): .. deprecated:: 1.6 Add/modify history metadata within - attr:`~iris.cube.Cube.attributes` as needed. + :attr:`~iris.cube.Cube.attributes` as needed. """ warn_deprecated("Cube.add_history() has been deprecated - " diff --git a/lib/iris/tests/test_coord_api.py b/lib/iris/tests/test_coord_api.py index cff8c5664f..705800c8e0 100644 --- a/lib/iris/tests/test_coord_api.py +++ b/lib/iris/tests/test_coord_api.py @@ -638,30 +638,44 @@ def test_get_set_points_and_bounds(self): coord.bounds = [[123, 456], [234, 567], [345, 678]] self.assertEqual(coord.shape, (3, )) self.assertEqual(coord.bounds.shape, (3, 2)) - + class TestGuessBounds(tests.IrisTest): def test_guess_bounds(self): - coord = iris.coords.DimCoord(np.array([0, 10, 20, 30]), long_name="foo", units="1") + coord = iris.coords.DimCoord(np.array([0, 10, 20, 30]), + long_name="foo", units="1") coord.guess_bounds() - self.assertArrayEqual(coord.bounds, np.array([[-5, 5], [5, 15], [15, 25], [25, 35]])) - + self.assertArrayEqual(coord.bounds, np.array([[-5, 5], + [5, 15], + [15, 25], + [25, 35]])) + coord.bounds = None coord.guess_bounds(0.25) - self.assertArrayEqual(coord.bounds, np.array([[-5, 5], [5, 15], [15, 25], [25, 35]]) + 2.5) - + self.assertArrayEqual(coord.bounds, np.array([[-5, 5], + [5, 15], + [15, 25], + [25, 35]]) + 2.5) + coord.bounds = None coord.guess_bounds(0.75) - self.assertArrayEqual(coord.bounds, np.array([[-5, 5], [5, 15], [15, 25], [25, 35]]) - 2.5) + self.assertArrayEqual(coord.bounds, np.array([[-5, 5], + [5, 15], + [15, 25], + [25, 35]]) - 2.5) points = coord.points.copy() points[2] = 25 coord.points = points coord.bounds = None coord.guess_bounds() - self.assertArrayEqual(coord.bounds, np.array([[-5., 5.], [5., 17.5], [17.5, 27.5], [27.5, 32.5]])) - + 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 = coord.points.copy() points[2] = 32 coord = iris.coords.AuxCoord.from_coord(coord) coord.points = points diff --git a/lib/iris/tests/unit/analysis/cartography/test_project.py b/lib/iris/tests/unit/analysis/cartography/test_project.py index 4adf824107..e627e9cfd1 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_project.py +++ b/lib/iris/tests/unit/analysis/cartography/test_project.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of Iris. # @@ -151,9 +151,9 @@ def test_no_coord_system(self): cube.coord('grid_latitude').coord_system = None with iris.tests.mock.patch('warnings.warn') as warn: _, _ = project(cube, ROBINSON) - warn.assert_called_once_with('Coordinate system of latitude and ' - 'longitude coordinates is not specified. ' - 'Assuming WGS84 Geodetic.') + msg = ('Coordinate system of latitude and longitude coordinates is ' + 'not specified. Assuming WGS84 Geodetic.') + self.assertIn(msg, warn.call_args_list[0][0][0]) if __name__ == '__main__': 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..89a5116454 --- /dev/null +++ b/lib/iris/tests/unit/coords/test_AuxCoord.py @@ -0,0 +1,120 @@ +# (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 numpy as np + +import biggus +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 = biggus.NumpyArrayAdapter(points) + + bounds = np.array([[1, 2], [2, 3]]) + bounds = biggus.NumpyArrayAdapter(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, biggus.NumpyArrayAdapter) + self.assertIsInstance(sliced_coord._bounds, biggus.NumpyArrayAdapter) + + # Original coord remains unrealised. + self.assertIsInstance(points, biggus.NumpyArrayAdapter) + self.assertIsInstance(bounds, biggus.NumpyArrayAdapter) + + +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..02eb43a20a --- /dev/null +++ b/lib/iris/tests/unit/coords/test_DimCoord.py @@ -0,0 +1,125 @@ +# (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 numpy as np + +import biggus +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 = biggus.NumpyArrayAdapter(points) + + bounds = np.array([[1, 2], [2, 3]]) + bounds = biggus.NumpyArrayAdapter(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, biggus.NumpyArrayAdapter) + self.assertIsInstance(bounds, biggus.NumpyArrayAdapter) + + +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 1b421f3c33..06a959bbb5 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2013 - 2016, Met Office +# (C) British Crown Copyright 2013 - 2017, Met Office # # This file is part of Iris. # @@ -1329,6 +1329,54 @@ def test_remove_cell_measure(self): [[self.b_cell_measure, (0, 1)]]) +class Test___getitem__nofuture(tests.IrisTest): + def setUp(self): + patch = mock.patch('iris.FUTURE.share_data', new=False) + self.mock_fshare = patch.start() + self.addCleanup(patch.stop) + + 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) + + def test_deprecation_warning(self): + warn_call = mock.patch('iris.cube.warn_deprecated') + cube = Cube(np.arange(6).reshape(2, 3)) + with warn_call as mocked_warn_call: + cube[1:] + msg = ('The `data` attribute of the indexing result is currently a ' + 'copy of the original data array. This behaviour was ' + 'deprecated in v1.12.0') + self.assertIn(msg, mocked_warn_call.call_args[0][0]) + + +class Test___getitem__future(tests.IrisTest): + def setUp(self): + patch = mock.patch('iris.FUTURE.share_data', new=True) + self.mock_fshare = patch.start() + self.addCleanup(patch.stop) + + 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.assertIs(cube.data.base, cube2.data.base) + + class Test__getitem_CellMeasure(tests.IrisTest): def setUp(self): cube = Cube(np.arange(6).reshape(2, 3)) diff --git a/lib/iris/tests/unit/test_Future.py b/lib/iris/tests/unit/test_Future.py index d55a9ae58f..ad6ee8a672 100644 --- a/lib/iris/tests/unit/test_Future.py +++ b/lib/iris/tests/unit/test_Future.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2013 - 2016, Met Office +# (C) British Crown Copyright 2013 - 2017, Met Office # # This file is part of Iris. # @@ -51,6 +51,12 @@ def test_valid_clip_latitudes(self): future.clip_latitudes = new_value self.assertEqual(future.clip_latitudes, new_value) + def test_valid_share_data(self): + future = Future() + new_value = not future.share_data + future.share_data = new_value + self.assertEqual(future.share_data, new_value) + def test_invalid_attribute(self): future = Future() with self.assertRaises(AttributeError): From 00697e8c73fdf5c6194fb4508803702d944bae50 Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 17 Feb 2017 11:18:24 +0000 Subject: [PATCH 2/4] MAINT: Updated to deprecation 1.13 --- .../deprecate_2017-Feb-17_share_data.txt} | 0 lib/iris/cube.py | 4 ++-- lib/iris/tests/unit/cube/test_Cube.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename docs/iris/src/whatsnew/{contributions_1.12/deprecate_2016-Dec-08_share_data.txt => contributions_1.13/deprecate_2017-Feb-17_share_data.txt} (100%) diff --git a/docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt b/docs/iris/src/whatsnew/contributions_1.13/deprecate_2017-Feb-17_share_data.txt similarity index 100% rename from docs/iris/src/whatsnew/contributions_1.12/deprecate_2016-Dec-08_share_data.txt rename to docs/iris/src/whatsnew/contributions_1.13/deprecate_2017-Feb-17_share_data.txt diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 756f9745d3..f54af312cf 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2153,7 +2153,7 @@ def __getitem__(self, keys): requested must be applicable directly to the cube.data attribute. All metadata will be subsequently indexed appropriately. - .. deprecated:: 1.12 + .. deprecated:: 1.13 In future, the `data` attribute of the indexing result may be a view onto the original data array, to avoid unnecessary copying. For the present, however, indexing always produces a full @@ -2200,7 +2200,7 @@ def new_cell_measure_dims(cm_): # not already our own. msg = ('The `data` attribute of the indexing result is currently ' 'a copy of the original data array. This behaviour was ' - 'deprecated in v1.12.0 and will be replaced with views ' + 'deprecated in v1.13.0 and will be replaced with views ' 'onto the original data array where possible. Set ' 'iris.FUTURE.share_data=True to switch to the new ' 'behaviour.') diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 06a959bbb5..77f1f46b8a 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1354,7 +1354,7 @@ def test_deprecation_warning(self): cube[1:] msg = ('The `data` attribute of the indexing result is currently a ' 'copy of the original data array. This behaviour was ' - 'deprecated in v1.12.0') + 'deprecated in v1.13.0') self.assertIn(msg, mocked_warn_call.call_args[0][0]) From a35908b67c4f0a53f379c2c11d9a9928886f3170 Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 17 Feb 2017 12:53:52 +0000 Subject: [PATCH 3/4] TEST: Fix failing python3 error --- .../analysis/geometry/test_geometry_area_weights.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py b/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py index ff34d9566d..ef7dbe431b 100644 --- a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py +++ b/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2015, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of Iris. # @@ -143,9 +143,11 @@ def test_distinct_xy_bounds_pole(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") # always trigger all warnings weights = geometry_area_weights(cube, geometry) - self.assertEqual(str(w[-1].message), "The geometry exceeds the " - "cube's y dimension at the upper end.") - self.assertTrue(issubclass(w[-1].category, UserWarning)) + + msg = ("The geometry exceeds the cube's y dimension at the upper " + "end.") + ind = [str(rec.message) for rec in w].index(msg) + self.assertTrue(issubclass(w[ind].category, UserWarning)) target = np.array([ [0, half, half, 0], [0, half, half, 0], From d78130dc0a249735909bb7924efec329dc652371 Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 24 Mar 2017 09:03:30 +0000 Subject: [PATCH 4/4] DOC: Future import in example code --- docs/iris/example_code/General/SOI_filtering.py | 3 +++ docs/iris/example_code/General/anomaly_log_colouring.py | 3 +++ docs/iris/example_code/General/cross_section.py | 3 +++ docs/iris/example_code/General/custom_aggregation.py | 3 +++ docs/iris/example_code/General/inset_plot.py | 3 +++ docs/iris/example_code/General/lineplot_with_legend.py | 3 +++ docs/iris/example_code/General/orca_projection.py | 3 +++ docs/iris/example_code/General/polynomial_fit.py | 3 +++ docs/iris/example_code/Meteorology/COP_1d_plot.py | 3 +++ docs/iris/example_code/Meteorology/COP_maps.py | 3 +++ docs/iris/example_code/Meteorology/deriving_phenomena.py | 3 +++ docs/iris/example_code/Meteorology/hovmoller.py | 3 +++ docs/iris/example_code/Meteorology/lagged_ensemble.py | 3 +++ docs/iris/example_code/Oceanography/atlantic_profiles.py | 3 +++ 14 files changed, 42 insertions(+) diff --git a/docs/iris/example_code/General/SOI_filtering.py b/docs/iris/example_code/General/SOI_filtering.py index 4d692811e6..62bf4fdeac 100644 --- a/docs/iris/example_code/General/SOI_filtering.py +++ b/docs/iris/example_code/General/SOI_filtering.py @@ -52,6 +52,9 @@ def low_pass_weights(window, cutoff): def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/General/anomaly_log_colouring.py b/docs/iris/example_code/General/anomaly_log_colouring.py index 72893f3512..ad140212b3 100644 --- a/docs/iris/example_code/General/anomaly_log_colouring.py +++ b/docs/iris/example_code/General/anomaly_log_colouring.py @@ -36,6 +36,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/General/cross_section.py b/docs/iris/example_code/General/cross_section.py index ab9025ff99..e61ef0fb48 100644 --- a/docs/iris/example_code/General/cross_section.py +++ b/docs/iris/example_code/General/cross_section.py @@ -15,6 +15,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/General/custom_aggregation.py b/docs/iris/example_code/General/custom_aggregation.py index 9918aae0d6..c772cf9c0a 100644 --- a/docs/iris/example_code/General/custom_aggregation.py +++ b/docs/iris/example_code/General/custom_aggregation.py @@ -65,6 +65,9 @@ def count_spells(data, threshold, axis, spell_length): def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/General/inset_plot.py b/docs/iris/example_code/General/inset_plot.py index 51abee641b..1a8669cc2d 100644 --- a/docs/iris/example_code/General/inset_plot.py +++ b/docs/iris/example_code/General/inset_plot.py @@ -17,6 +17,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Load the data with iris.FUTURE.context(netcdf_promote=True): cube1 = iris.load_cube(iris.sample_data_path('ostia_monthly.nc')) diff --git a/docs/iris/example_code/General/lineplot_with_legend.py b/docs/iris/example_code/General/lineplot_with_legend.py index 4ce80a9569..3caa9c3eee 100644 --- a/docs/iris/example_code/General/lineplot_with_legend.py +++ b/docs/iris/example_code/General/lineplot_with_legend.py @@ -11,6 +11,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + fname = iris.sample_data_path('air_temp.pp') # Load exactly one cube from the given file. diff --git a/docs/iris/example_code/General/orca_projection.py b/docs/iris/example_code/General/orca_projection.py index abe5b19c8e..9b583cb120 100644 --- a/docs/iris/example_code/General/orca_projection.py +++ b/docs/iris/example_code/General/orca_projection.py @@ -22,6 +22,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/General/polynomial_fit.py b/docs/iris/example_code/General/polynomial_fit.py index 4737e275b9..9e92e63001 100644 --- a/docs/iris/example_code/General/polynomial_fit.py +++ b/docs/iris/example_code/General/polynomial_fit.py @@ -16,6 +16,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/Meteorology/COP_1d_plot.py b/docs/iris/example_code/Meteorology/COP_1d_plot.py index 19489f506b..2be0984316 100644 --- a/docs/iris/example_code/Meteorology/COP_1d_plot.py +++ b/docs/iris/example_code/Meteorology/COP_1d_plot.py @@ -36,6 +36,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/Meteorology/COP_maps.py b/docs/iris/example_code/Meteorology/COP_maps.py index 636e369332..9f90768b3b 100644 --- a/docs/iris/example_code/Meteorology/COP_maps.py +++ b/docs/iris/example_code/Meteorology/COP_maps.py @@ -50,6 +50,9 @@ def cop_metadata_callback(cube, field, filename): def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Load e1 and a1 using the callback to update the metadata e1 = iris.load_cube(iris.sample_data_path('E1.2098.pp'), callback=cop_metadata_callback) diff --git a/docs/iris/example_code/Meteorology/deriving_phenomena.py b/docs/iris/example_code/Meteorology/deriving_phenomena.py index d34a2c8b78..cb6c70e21a 100644 --- a/docs/iris/example_code/Meteorology/deriving_phenomena.py +++ b/docs/iris/example_code/Meteorology/deriving_phenomena.py @@ -38,6 +38,9 @@ def limit_colorbar_ticks(contour_object): def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + fname = iris.sample_data_path('colpex.pp') # The list of phenomena of interest diff --git a/docs/iris/example_code/Meteorology/hovmoller.py b/docs/iris/example_code/Meteorology/hovmoller.py index eef0d4be33..1966346ae9 100644 --- a/docs/iris/example_code/Meteorology/hovmoller.py +++ b/docs/iris/example_code/Meteorology/hovmoller.py @@ -17,6 +17,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True diff --git a/docs/iris/example_code/Meteorology/lagged_ensemble.py b/docs/iris/example_code/Meteorology/lagged_ensemble.py index 7898d26392..0f6e0d9ff2 100644 --- a/docs/iris/example_code/Meteorology/lagged_ensemble.py +++ b/docs/iris/example_code/Meteorology/lagged_ensemble.py @@ -44,6 +44,9 @@ def realization_metadata(cube, field, fname): def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # extract surface temperature cubes which have an ensemble member # coordinate, adding appropriate lagged ensemble metadata surface_temp = iris.load_cube( diff --git a/docs/iris/example_code/Oceanography/atlantic_profiles.py b/docs/iris/example_code/Oceanography/atlantic_profiles.py index 3d3f59e8c5..51d12666d4 100644 --- a/docs/iris/example_code/Oceanography/atlantic_profiles.py +++ b/docs/iris/example_code/Oceanography/atlantic_profiles.py @@ -22,6 +22,9 @@ def main(): + # Use new behaviour of data-sharing behaviour on indexing. + iris.FUTURE.share_data = True + # Enable a future option, to ensure that the netcdf load works the same way # as in future Iris versions. iris.FUTURE.netcdf_promote = True