Repository: numpy/numpy

Issue:
Assigning a fill_value in masked array fails silently for wrong type
I try to assign a `fill_value` to a masked array. If it's the right type (e.g. an `int` in an array of type `int`), it works, but if I try to assign a `fill_value` of the wrong dtype (e.g. a `string` in an array of type `int`, it fails silently. I don't know if this is a bug or a feature, but I had expected a `ValueError` or something similar if the value cannot be broadcasted to the required type.

```
In [1]: x = np.ma.array(np.arange(3).reshape(3), mask=[[1, 0, 0]])

In [2]: print repr(x)
masked_array(data = [-- 1 2],
             mask = [ True False False],
       fill_value = 999999)


In [3]: x.fill_value = 'n.a.'

In [4]: print repr(x)
masked_array(data = [-- 1 2],
             mask = [ True False False],
       fill_value = 999999)


In [5]: x.fill_value = -99

In [6]: print repr(x)
masked_array(data = [-- 1 2],
             mask = [ True False False],
       fill_value = -99)
```



Discussion:
I think your .reshape(3) is redundant.   x = np.ma.array(np.arange(3), mask=[[1, 0, 0]]) should produce the same array.

The current logic essentially boils down to:

```
    if isinstance(fill_value, basestring) and (ndtype.char not in 'SVU'):
        fill_value = default_fill_value(ndtype)
    else:
        # In case we want to convert 1e+20 to int...
        try:
            fill_value = np.array(fill_value, copy=False, dtype=ndtype)#.item()
        except OverflowError:
            fill_value = default_fill_value(ndtype)
```

Note that for your example, the ndtype.char is 'l' on a 64bit machine.

The above essentially results in the default fill value being used, both for your example and for when overflows occur. In my opinion, a TypeError would be a more appropriate behaviour - and silently setting the default for the overflow case could be frustrating for a novice working with masked arrays.

I was not quiet sure what I expected in my example, but now as you say it: Yes, a `TypeError` is exactly what I should have gotten.
I can see the use case for catching the overflow error though. If you get an `OverflowError` that probably means you wanted a "really larger number" but did not know what the largest possible number is for the current machine precision.

The simpler

```
        try:
            fill_value = np.array(fill_value, copy=False, dtype=ndtype)#.item()
        except OverflowError:
            fill_value = default_fill_value(ndtype)
```

would give me a `ValueError` with a fairly descriptive error message.

```
----> 1 np.array("g", copy=False, dtype=np.float)

ValueError: could not convert string to float: g
```

I think that that would be good solution.

However, I wonder why the current implementation is the way it is but I cannot find the corresponding issue or ticket.

I propose that the following is implemented:
- if the dtype is numeric but the fill_value is set to a string then a TypeError is raised.
- if the fill_value is set to a value that overflows the dtype then an OverflowError is raised.

If this seems OK with you, I'm happy to put together a pull request for this.

This sounds good to me and thanks a lot for making a pull request!


Resolving patch:
diff --git a/numpy/ma/core.py b/numpy/ma/core.py
--- a/numpy/ma/core.py
+++ b/numpy/ma/core.py
@@ -417,13 +417,18 @@ def _check_fill_value(fill_value, ndtype):
                                   dtype=ndtype)
     else:
         if isinstance(fill_value, basestring) and (ndtype.char not in 'OSVU'):
-            fill_value = default_fill_value(ndtype)
+            err_msg = "Cannot set fill value of string with array of dtype %s"
+            raise TypeError(err_msg % ndtype)
         else:
-            # In case we want to convert 1e+20 to int...
+            # In case we want to convert 1e20 to int...
             try:
-                fill_value = np.array(fill_value, copy=False, dtype=ndtype)#.item()
+                fill_value = np.array(fill_value, copy=False, dtype=ndtype)
             except OverflowError:
-                fill_value = default_fill_value(ndtype)
+                # Raise TypeError instead of OverflowError. OverflowError
+                # is seldom used, and the real problem here is that the
+                # passed fill_value is not compatible with the ndtype.
+                err_msg = "Fill value %s overflows dtype %s"
+                raise TypeError(err_msg % (fill_value, ndtype))
     return np.array(fill_value)
 
 


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
@@ -1312,9 +1312,9 @@ def test_flat(self):
 
 #------------------------------------------------------------------------------
 class TestFillingValues(TestCase):
-    #
+
     def test_check_on_scalar(self):
-        # Test _check_fill_value
+        # Test _check_fill_value set to valid and invalid values
         _check_fill_value = np.ma.core._check_fill_value
         #
         fval = _check_fill_value(0, int)
@@ -1326,9 +1326,8 @@ def test_check_on_scalar(self):
         assert_equal(fval, asbytes("0"))
         fval = _check_fill_value(None, "|S3")
         assert_equal(fval, default_fill_value("|S3"))
-        #
-        fval = _check_fill_value(1e+20, int)
-        assert_equal(fval, default_fill_value(0))
+        self.assertRaises(TypeError, _check_fill_value, 1e+20, int)
+        self.assertRaises(TypeError, _check_fill_value, 'stuff', int)
 
     def test_check_on_fields(self):
         # Tests _check_fill_value with records