-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
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
Conversation
numpy/ma/core.py
Outdated
@@ -3351,6 +3354,8 @@ def __setattr__(self, attr, value): | |||
self._mask.shape = self.shape | |||
except (AttributeError, TypeError): | |||
pass | |||
elif attr == 'shape' and getmask(self) is not nomask: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be clearer to add dtype
and shape
@properties
that fall back on ndarray.shape.__get__
etc
numpy/ma/tests/test_old_ma.py
Outdated
@@ -273,7 +273,8 @@ def test_testCopySize(self): | |||
assert_(y1.mask is m) | |||
|
|||
y1a = array(y1, copy=0) | |||
assert_(y1a.mask is y1.mask) | |||
assert_(y1a._mask.__array_interface__ == | |||
y1._mask.__array_interface__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... I'd actually expect the stronger result of y1a is y1
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is #4043, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment referencing that issue here?
numpy/ma/tests/test_core.py
Outdated
@@ -397,7 +397,8 @@ def test_copy(self): | |||
y1a = array(y1) | |||
assert_(y1a._data.__array_interface__ == |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, what? This doesn't seem sensible to me at all (#10318)
@eric-wieser - I agree the changes in behaviour for Also agreed that setters for |
e39107a
to
97628c2
Compare
@eric-wieser - your comment reminded me about the existence of this branch... I now added a second commit replacing I also still need to think of a test case that shows this actually prevents any problems! |
numpy/ma/core.py
Outdated
|
||
@shape.setter | ||
def shape(self, shape): | ||
super(MaskedArray, MaskedArray).shape.__set__(self, shape) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be just super(MaskedArray).shape
or perhaps just ndarray.shape
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: I'm wrong. TIL how super(cls, cls)
works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this annoyance is in part why I wondered if it was a good idea to replace __setattr__
with this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The advantage is that subclasses are now able to call Maskedarray.shape.__set__
in the unlikely even they override shape, and it won't accidentally call ndarray.shape.__set__
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think this should be super(MaskedArray, type(self))
, which correctly walks the MRO.
Is this enough for a test? On >>> a = np.ma.array([1, 2, 3, 4], mask=True)
>>> d = a.data
>>> m = a.mask
>>> a.shape = (2, 2)
>>> d
array([1, 2, 3, 4])
>>> m # your patch stops this changing shape, which is consistent with d
array([[ True, True],
[ True, True]], dtype=bool) |
Hmm, that particular case is not solved by this PR - it needs your original one, where the mask is always viewed. Indeed, I found it very hard to far to get to a case where my PR matters... I'll try a bit more, but if it fails, perhaps best to split it into two - and at least get rid of the |
Ok, we now have a concrete problem caused by this, as documented in #3140 |
Since dtype and shape are properties, this needs a somewhat ugly super construction; see https://bugs.python.org/issue14965
97628c2
to
b779bae
Compare
Thanks for realizing there was a test case after all! I now added it, and rebased for good measure (and fixed the |
try: | ||
self._mask.shape = self.shape | ||
except (AttributeError, TypeError): | ||
pass | ||
|
||
@property | ||
def shape(self): | ||
return super(MaskedArray, self).shape |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Thanks @mhvk! |
__array_finalize__
cannot back-mangle shape
Just a small step, but
__array_finalize__
should not be allowed to change the shape of anyobj
passed in. Arguably, it should not change the shape of the mask at all, but removing that currently breaks quite a bit more, so perhaps it is best to try to remedy that in a second step.Need to think a bit about a test case that shows that this actually prevents problems.