Repository: numpy/numpy

Issue:
Converting masked array from float to uint8 does not convert dtype of fill_value
When a masked array of dtype float32 is converted to uint8, the dtype of the fill_value persists. When setting the fill_value to a uint8 value, the value is converted to float. Filling such an array yields an array of dtype object.

This was working in numpy==1.14.

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np

foo = np.ma.masked_equal(np.arange(10, dtype="float32"), 5)
foo.fill_value = -1.0
bar = foo.astype("uint8")
print(bar.fill_value)
bar.fill_value = 255
print(bar.fill_value)
print(bar.filled().dtype)
```
### Error message:
with numpy==1.14.2 this yields:
```
255
255
uint8
```

with numpy==1.15.2 this yields:
```
-1.0
255.0
object
```

### Numpy/Python version information:
Tested with python 2.7 and 3.5.2
numpy==1.15
<!-- Output from 'import sys, numpy; print(numpy.__version__, sys.version)' -->




Discussion:
@eric-wieser ISTR that you made some changes here.
I think it is probably #10211 from @mhvk.
I fear that this regression is indeed my fault...  I'll try to look at it, but as I am a bit overwhelmed right now, I fear it may be a while. The easiest would be to reintroduce the `astype` override, which just calls `super` for the main work, and then keeps the deleted part about `fill_value` (an alternative might add code to `__array_finalize__`). But this also needs to add some tests, since clearly our existing coverage did not catch this.
I'll take care of it.
> since clearly our existing coverage did not catch this.

@mhvk  Yes, we do a lot of refactoring these days and it exposes the lack of complete test coverage, especially in unloved packages like masked arrays. My experience with tests is that when I make them more complete they invariably turn up bugs.

Resolving patch:
diff --git a/numpy/ma/core.py b/numpy/ma/core.py
--- a/numpy/ma/core.py
+++ b/numpy/ma/core.py
@@ -3008,11 +3008,13 @@ def __array_finalize__(self, obj):
             except (TypeError, AttributeError):
                 # When _mask.shape is not writable (because it's a void)
                 pass
-        # Finalize the fill_value for structured arrays
-        if self.dtype.names is not None:
-            if self._fill_value is None:
-                self._fill_value = _check_fill_value(None, self.dtype)
-        return
+
+        # Finalize the fill_value
+        if self._fill_value is not None:
+            self._fill_value = _check_fill_value(self._fill_value, self.dtype)
+        elif self.dtype.names is not None:
+            # Finalize the default fill_value for structured arrays
+            self._fill_value = _check_fill_value(None, self.dtype)
 
     def __array_wrap__(self, obj, context=None):
         """


Test patch:
diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py
--- a/numpy/ma/tests/test_core.py
+++ b/numpy/ma/tests/test_core.py
@@ -4985,7 +4985,7 @@ def test_ufunc_with_out_varied():
     assert_equal(res_pos.data, expected.data)
 
 
-def test_astype():
+def test_astype_mask_ordering():
     descr = [('v', int, 3), ('x', [('y', float)])]
     x = array([
         [([1, 2, 3], (1.0,)),  ([1, 2, 3], (2.0,))],
@@ -5017,6 +5017,28 @@ def test_astype():
     assert_(x_f2.mask.flags.f_contiguous)
 
 
+dts = [np.dtype(dt_) for dt_ in '?bhilqBHILQefdgFD']
+ids = [dt_.char for dt_ in dts]
+
+@pytest.mark.parametrize('dt1', dts, ids=ids)
+@pytest.mark.parametrize('dt2', dts, ids=ids)
+@pytest.mark.filterwarnings('ignore::numpy.ComplexWarning')
+def test_astype_basic(dt1, dt2):
+    # See gh-12070
+    src = np.ma.array(ones(3, dt1), fill_value=1)
+    dst = src.astype(dt2)
+
+    assert_(src.fill_value == 1)
+    assert_(src.dtype == dt1)
+    assert_(src.fill_value.dtype == dt1)
+
+    assert_(dst.fill_value == 1)
+    assert_(dst.dtype == dt2)
+    assert_(dst.fill_value.dtype == dt2)
+
+    assert_equal(src, dst)
+
+
 def test_fieldless_void():
     dt = np.dtype([])  # a void dtype with no fields
     x = np.empty(4, dt)