Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 21 additions & 3 deletions lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpelley This comment doesn't make sense.

if self.has_lazy_data():
_ = self.data
Copy link
Member

@bjlittle bjlittle May 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpelley I really disagree that if you want to share the data, you need to realise the data here at this point in the property. This is bad practice IMHO.

You're effectively applying a side-effect here to suit your specific needs. Surely, if you do require to realise the data, then it should only occur in the code base where share_data is True and it actually needs the data at that point in time. We're now in the situation where we've thrown away any laziness, all because we've set this property .... and the kicker is that the data is loaded regardless of the value passed ... I find that totally bizarre!

Loading the data is a BIG deal, and it's not even advertised in the doc-string. Why?

... and where's the documentation to cover this change, so that users know what's happening? No doc-string, no documentation. We really need to be always conscious of our users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello @bjlittle

I explicitly asked for this to be included, as part of my review of the code, so I am more culpable than @cpelley

I think that you are right that this should only be implemented for setting to True
I think that a better docstring would help

I have created #2584, pointed at 1.13.x, as a proposed 'bug fix' for these concerns

self._share_data = bool(value)

@property
def metadata(self):
"""
Expand Down Expand Up @@ -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]
Expand All @@ -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):
Expand Down
48 changes: 48 additions & 0 deletions lib/iris/tests/unit/cube/test_Cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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))
Expand Down