Skip to content

BUG: Ensure __array_finalize__ cannot back-mangle shape #10314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 29, 2018
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
34 changes: 27 additions & 7 deletions numpy/ma/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2999,7 +2999,9 @@ def __array_finalize__(self, obj):
order = "K"

_mask = _mask.astype(_mask_dtype, order)

else:
# Take a view so shape changes, etc., do not propagate back.
_mask = _mask.view()
else:
_mask = nomask

Expand Down Expand Up @@ -3344,17 +3346,35 @@ def __setitem__(self, indx, value):
_mask[indx] = mindx
return

def __setattr__(self, attr, value):
super(MaskedArray, self).__setattr__(attr, value)
if attr == 'dtype' and self._mask is not nomask:
self._mask = self._mask.view(make_mask_descr(value), ndarray)
# Try to reset the shape of the mask (if we don't have a void)
# This raises a ValueError if the dtype change won't work
# Define so that we can overwrite the setter.
@property
def dtype(self):
return super(MaskedArray, self).dtype

@dtype.setter
def dtype(self, dtype):
super(MaskedArray, type(self)).dtype.__set__(self, dtype)
if self._mask is not nomask:
self._mask = self._mask.view(make_mask_descr(dtype), ndarray)
# Try to reset the shape of the mask (if we don't have a void).
# This raises a ValueError if the dtype change won't work.
try:
self._mask.shape = self.shape
except (AttributeError, TypeError):
pass

@property
def shape(self):
return super(MaskedArray, self).shape
Copy link
Member

@eric-wieser eric-wieser May 29, 2018

Choose a reason for hiding this comment

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

Pretty sure this needs to be super(MaskedArray, type(self)).shape.__get__(self, type(self)), unless there's something weird about base classes in C

Copy link
Member

Choose a reason for hiding this comment

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

Ok, seems there's something weird about base classes. If this were shadowing another property (rather than a getset_descriptor) , you'd need what I have above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's true - I;m fairly sure one can shadow a property getter as I have it above, just not the setter.


@shape.setter
def shape(self, shape):
super(MaskedArray, type(self)).shape.__set__(self, shape)
# Cannot use self._mask, since it may not (yet) exist when a
# masked matrix sets the shape.
if getmask(self) is not nomask:
self._mask.shape = self.shape

def __setmask__(self, mask, copy=False):
"""
Set the mask.
Expand Down
4 changes: 3 additions & 1 deletion numpy/ma/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,11 @@ def test_copy(self):
assert_equal(y1._mask.__array_interface__, m.__array_interface__)

y1a = array(y1)
# Default for masked array is not to copy; see gh-10318.
assert_(y1a._data.__array_interface__ ==
y1._data.__array_interface__)
assert_(y1a.mask is y1.mask)
assert_(y1a._mask.__array_interface__ ==
y1._mask.__array_interface__)

y2 = array(x1, mask=m3)
assert_(y2._data.__array_interface__ == x1.__array_interface__)
Expand Down
6 changes: 5 additions & 1 deletion numpy/ma/tests/test_old_ma.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,11 @@ def test_testCopySize(self):
assert_(y1.mask is m)

y1a = array(y1, copy=0)
assert_(y1a.mask is y1.mask)
# For copy=False, one might expect that the array would just
# passed on, i.e., that it would be "is" instead of "==".
# See gh-4043 for discussion.
assert_(y1a._mask.__array_interface__ ==
y1._mask.__array_interface__)

y2 = array(x1, mask=m3, copy=0)
assert_(y2.mask is m3)
Expand Down
10 changes: 10 additions & 0 deletions numpy/ma/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,13 @@ def test_ddof_corrcoef(self):
r1 = np.ma.corrcoef(x, y, ddof=1)
# ddof should not have an effect (it gets cancelled out)
assert_allclose(r0.data, r1.data)

def test_mask_not_backmangled(self):
# See gh-10314. Test case taken from gh-3140.
a = np.ma.MaskedArray([1., 2.], mask=[False, False])
assert_(a.mask.shape == (2,))
b = np.tile(a, (2, 1))
# Check that the above no longer changes a.shape to (1, 2)
assert_(a.mask.shape == (2,))
assert_(b.shape == (2, 2))
assert_(b.mask.shape == (2, 2))