diff options
-rw-r--r-- | azure-pipelines.yml | 26 | ||||
-rw-r--r-- | doc/release/upcoming_changes/15802.expired.rst | 9 | ||||
-rw-r--r-- | doc/release/upcoming_changes/15804.expired.rst | 8 | ||||
-rw-r--r-- | doc/release/upcoming_changes/15805.expired.rst | 6 | ||||
-rw-r--r-- | doc/source/reference/ufuncs.rst | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtype_transfer.c | 77 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 11 | ||||
-rw-r--r-- | numpy/core/tests/test_regression.py | 13 | ||||
-rw-r--r-- | numpy/f2py/tests/util.py | 5 | ||||
-rw-r--r-- | numpy/lib/function_base.py | 64 | ||||
-rw-r--r-- | numpy/lib/tests/test_function_base.py | 44 | ||||
-rw-r--r-- | numpy/ma/core.py | 15 | ||||
-rw-r--r-- | numpy/ma/tests/test_core.py | 15 |
13 files changed, 153 insertions, 150 deletions
diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6b1ff4c28..20a240236 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -218,18 +218,34 @@ stages: OPENBLAS_SUFFIX: '64_' steps: - template: azure-steps-windows.yml - - job: Linux_PyPy3 + # - job: Linux_PyPy3 + # pool: + # vmIMage: 'ubuntu-18.04' + # steps: + # - script: source tools/pypy-test.sh + # displayName: 'Run PyPy3 Build / Tests' + # - task: PublishTestResults@2 + # condition: succeededOrFailed() + # inputs: + # testResultsFiles: '**/test-*.xml' + # testRunTitle: 'Publish test results for PyPy3' + # failTaskOnFailedTests: true + - job: Linux_18_04 pool: - vmIMage: 'ubuntu-18.04' + vmImage: 'ubuntu-18.04' steps: - - script: source tools/pypy-test.sh - displayName: 'Run PyPy3 Build / Tests' + - script: | + python3 -m pip install --user --upgrade pip setuptools + python3 -m pip install --user -r test_requirements.txt + CPPFLAGS='' F77=gfortran-5 F90=gfortran-5 \ + python3 runtests.py --debug-info --mode=full -- -rsx --junitxml=junit/test-results.xml + displayName: 'Run Linux 18.04 Build / Tests' - task: PublishTestResults@2 condition: succeededOrFailed() inputs: testResultsFiles: '**/test-*.xml' - testRunTitle: 'Publish test results for PyPy3' failTaskOnFailedTests: true + testRunTitle: 'Publish test results for gcc 4.8' - job: Linux_gcc48 pool: vmImage: 'ubuntu-18.04' diff --git a/doc/release/upcoming_changes/15802.expired.rst b/doc/release/upcoming_changes/15802.expired.rst new file mode 100644 index 000000000..1a1b373a7 --- /dev/null +++ b/doc/release/upcoming_changes/15802.expired.rst @@ -0,0 +1,9 @@ +`numpy.insert` and `numpy.delete` can no longer be passed an axis on 0d arrays +------------------------------------------------------------------------------ +This concludes a deprecation from 1.9, where when an ``axis`` argument was +passed to a call to `~numpy.insert` and `~numpy.delete` on a 0d array, the +``axis`` and ``obj`` argument and indices would be completely ignored. +In these cases, ``insert(arr, "nonsense", 42, axis=0)`` would actually overwrite the +entire array, while ``delete(arr, "nonsense", axis=0)`` would be ``arr.copy()`` + +Now passing ``axis`` on a 0d array raises `~numpy.AxisError`. diff --git a/doc/release/upcoming_changes/15804.expired.rst b/doc/release/upcoming_changes/15804.expired.rst new file mode 100644 index 000000000..e110e1ead --- /dev/null +++ b/doc/release/upcoming_changes/15804.expired.rst @@ -0,0 +1,8 @@ +`numpy.delete` no longer ignores out-of-bounds indices +------------------------------------------------------ +This concludes deprecations from 1.8 and 1.9, where ``np.delete`` would ignore +both negative and out-of-bounds items in a sequence of indices. This was at +odds with its behavior when passed a single index. + +Now out-of-bounds items throw ``IndexError``, and negative items index from the +end. diff --git a/doc/release/upcoming_changes/15805.expired.rst b/doc/release/upcoming_changes/15805.expired.rst new file mode 100644 index 000000000..d317e98b8 --- /dev/null +++ b/doc/release/upcoming_changes/15805.expired.rst @@ -0,0 +1,6 @@ +`numpy.insert` and `numpy.delete` no longer accept non-integral indices +----------------------------------------------------------------------- +This concludes a deprecation from 1.9, where sequences of non-integers indices +were allowed and cast to integers. Now passing sequences of non-integral +indices raises ``IndexError``, just like it does when passing a single +non-integral scalar. diff --git a/doc/source/reference/ufuncs.rst b/doc/source/reference/ufuncs.rst index 20c89e0b3..aad285122 100644 --- a/doc/source/reference/ufuncs.rst +++ b/doc/source/reference/ufuncs.rst @@ -441,13 +441,13 @@ advanced usage and will not typically be used. *extobj* - a list of length 1, 2, or 3 specifying the ufunc buffer-size, the - error mode integer, and the error call-back function. Normally, these + a list of length 3 specifying the ufunc buffer-size, the error + mode integer, and the error call-back function. Normally, these values are looked up in a thread-specific dictionary. Passing them here circumvents that look up and uses the low-level specification - provided for the error mode. This may be useful, for example, as an - optimization for calculations requiring many ufunc calls on small arrays - in a loop. + provided for the error mode. This may be useful, for example, as + an optimization for calculations requiring many ufunc calls on + small arrays in a loop. diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index b26d5ac89..ecaa680ec 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -312,6 +312,7 @@ typedef struct { NpyAuxData *wrappeddata, *todata, *fromdata; npy_intp src_itemsize, dst_itemsize; char *bufferin, *bufferout; + npy_bool init_dest, out_needs_api; } _align_wrap_data; /* transfer data free function */ @@ -372,6 +373,9 @@ static NpyAuxData *_align_wrap_data_clone(NpyAuxData *data) } } + newdata->init_dest = d->init_dest; + newdata->out_needs_api = d->out_needs_api; + return (NpyAuxData *)newdata; } @@ -391,57 +395,26 @@ _strided_to_strided_contig_align_wrap(char *dst, npy_intp dst_stride, *todata = d->todata, *fromdata = d->fromdata; char *bufferin = d->bufferin, *bufferout = d->bufferout; + npy_bool init_dest = d->init_dest, out_needs_api = d->out_needs_api; for(;;) { - if (N > NPY_LOWLEVEL_BUFFER_BLOCKSIZE) { - tobuffer(bufferin, inner_src_itemsize, src, src_stride, - NPY_LOWLEVEL_BUFFER_BLOCKSIZE, - src_itemsize, todata); - wrapped(bufferout, dst_itemsize, bufferin, inner_src_itemsize, - NPY_LOWLEVEL_BUFFER_BLOCKSIZE, - inner_src_itemsize, wrappeddata); - frombuffer(dst, dst_stride, bufferout, dst_itemsize, - NPY_LOWLEVEL_BUFFER_BLOCKSIZE, - dst_itemsize, fromdata); - N -= NPY_LOWLEVEL_BUFFER_BLOCKSIZE; - src += NPY_LOWLEVEL_BUFFER_BLOCKSIZE*src_stride; - dst += NPY_LOWLEVEL_BUFFER_BLOCKSIZE*dst_stride; - } - else { - tobuffer(bufferin, inner_src_itemsize, src, src_stride, N, - src_itemsize, todata); - wrapped(bufferout, dst_itemsize, bufferin, inner_src_itemsize, N, - inner_src_itemsize, wrappeddata); - frombuffer(dst, dst_stride, bufferout, dst_itemsize, N, - dst_itemsize, fromdata); + /* + * The caller does not know if a previous call resulted in a Python + * exception. Much of the Python API is unsafe while an exception is in + * flight, so just skip all the work. Someone higher in the call stack + * will check for errors and propagate them. + */ + if (out_needs_api && PyErr_Occurred()) { return; } - } -} - -static void -_strided_to_strided_contig_align_wrap_init_dest(char *dst, npy_intp dst_stride, - char *src, npy_intp src_stride, - npy_intp N, npy_intp src_itemsize, - NpyAuxData *data) -{ - _align_wrap_data *d = (_align_wrap_data *)data; - PyArray_StridedUnaryOp *wrapped = d->wrapped, - *tobuffer = d->tobuffer, - *frombuffer = d->frombuffer; - npy_intp inner_src_itemsize = d->src_itemsize, - dst_itemsize = d->dst_itemsize; - NpyAuxData *wrappeddata = d->wrappeddata, - *todata = d->todata, - *fromdata = d->fromdata; - char *bufferin = d->bufferin, *bufferout = d->bufferout; - - for(;;) { if (N > NPY_LOWLEVEL_BUFFER_BLOCKSIZE) { tobuffer(bufferin, inner_src_itemsize, src, src_stride, NPY_LOWLEVEL_BUFFER_BLOCKSIZE, src_itemsize, todata); - memset(bufferout, 0, dst_itemsize*NPY_LOWLEVEL_BUFFER_BLOCKSIZE); + if (init_dest) { + memset(bufferout, 0, + dst_itemsize*NPY_LOWLEVEL_BUFFER_BLOCKSIZE); + } wrapped(bufferout, dst_itemsize, bufferin, inner_src_itemsize, NPY_LOWLEVEL_BUFFER_BLOCKSIZE, inner_src_itemsize, wrappeddata); @@ -455,7 +428,9 @@ _strided_to_strided_contig_align_wrap_init_dest(char *dst, npy_intp dst_stride, else { tobuffer(bufferin, inner_src_itemsize, src, src_stride, N, src_itemsize, todata); - memset(bufferout, 0, dst_itemsize*N); + if (init_dest) { + memset(bufferout, 0, dst_itemsize*N); + } wrapped(bufferout, dst_itemsize, bufferin, inner_src_itemsize, N, inner_src_itemsize, wrappeddata); frombuffer(dst, dst_stride, bufferout, dst_itemsize, N, @@ -477,6 +452,7 @@ _strided_to_strided_contig_align_wrap_init_dest(char *dst, npy_intp dst_stride, * wrapped - contig to contig transfer function being wrapped * wrappeddata - data for wrapped * init_dest - 1 means to memset the dest buffer to 0 before calling wrapped. + * out_needs_api - if NPY_TRUE, check for (and break on) Python API errors. * * Returns NPY_SUCCEED or NPY_FAIL. */ @@ -487,6 +463,7 @@ wrap_aligned_contig_transfer_function( PyArray_StridedUnaryOp *frombuffer, NpyAuxData *fromdata, PyArray_StridedUnaryOp *wrapped, NpyAuxData *wrappeddata, int init_dest, + int out_needs_api, PyArray_StridedUnaryOp **out_stransfer, NpyAuxData **out_transferdata) { @@ -519,14 +496,11 @@ wrap_aligned_contig_transfer_function( data->bufferin = (char *)data + basedatasize; data->bufferout = data->bufferin + NPY_LOWLEVEL_BUFFER_BLOCKSIZE*src_itemsize; + data->init_dest = (npy_bool) init_dest; + data->out_needs_api = (npy_bool) out_needs_api; /* Set the function and data */ - if (init_dest) { - *out_stransfer = &_strided_to_strided_contig_align_wrap_init_dest; - } - else { - *out_stransfer = &_strided_to_strided_contig_align_wrap; - } + *out_stransfer = &_strided_to_strided_contig_align_wrap; *out_transferdata = (NpyAuxData *)data; return NPY_SUCCEED; @@ -1171,6 +1145,7 @@ get_datetime_to_unicode_transfer_function(int aligned, frombuffer, fromdata, caststransfer, castdata, PyDataType_FLAGCHK(str_dtype, NPY_NEEDS_INIT), + *out_needs_api, out_stransfer, out_transferdata) != NPY_SUCCEED) { NPY_AUXDATA_FREE(castdata); NPY_AUXDATA_FREE(todata); @@ -1293,6 +1268,7 @@ get_unicode_to_datetime_transfer_function(int aligned, frombuffer, fromdata, caststransfer, castdata, PyDataType_FLAGCHK(dst_dtype, NPY_NEEDS_INIT), + *out_needs_api, out_stransfer, out_transferdata) != NPY_SUCCEED) { Py_DECREF(str_dtype); NPY_AUXDATA_FREE(castdata); @@ -1613,6 +1589,7 @@ get_cast_transfer_function(int aligned, frombuffer, fromdata, caststransfer, castdata, PyDataType_FLAGCHK(dst_dtype, NPY_NEEDS_INIT), + *out_needs_api, out_stransfer, out_transferdata) != NPY_SUCCEED) { NPY_AUXDATA_FREE(castdata); NPY_AUXDATA_FREE(todata); diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 829679dab..7b3397795 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -16,14 +16,7 @@ from contextlib import contextmanager from numpy.compat import pickle -try: - import pathlib -except ImportError: - try: - import pathlib2 as pathlib - except ImportError: - pathlib = None - +import pathlib import builtins from decimal import Decimal @@ -4680,14 +4673,12 @@ class TestIO: y = np.fromfile(self.filename, dtype=self.dtype) assert_array_equal(y, self.x.flat) - @pytest.mark.skipif(pathlib is None, reason="pathlib not found") def test_roundtrip_pathlib(self): p = pathlib.Path(self.filename) self.x.tofile(p) y = np.fromfile(p, dtype=self.dtype) assert_array_equal(y, self.x.flat) - @pytest.mark.skipif(pathlib is None, reason="pathlib not found") def test_roundtrip_dump_pathlib(self): p = pathlib.Path(self.filename) self.x.dump(p) diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 97898cc20..18d5b6032 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -1408,6 +1408,13 @@ class TestRegression: dtype='U') assert_raises(UnicodeEncodeError, np.array, a, 'S4') + def test_unicode_to_string_cast_error(self): + # gh-15790 + a = np.array([u'\x80'] * 129, dtype='U3') + assert_raises(UnicodeEncodeError, np.array, a, 'S') + b = a.reshape(3, 43)[:-1, :-1] + assert_raises(UnicodeEncodeError, np.array, b, 'S') + def test_mixed_string_unicode_array_creation(self): a = np.array(['1234', u'123']) assert_(a.itemsize == 16) @@ -1501,10 +1508,7 @@ class TestRegression: test_type(t) def test_buffer_hashlib(self): - try: - from hashlib import md5 - except ImportError: - from md5 import new as md5 + from hashlib import md5 x = np.array([1, 2, 3], dtype=np.dtype('<i4')) assert_equal(md5(x).hexdigest(), '2a1dd1e1e59d0a384c26951e316cd7e6') @@ -2481,4 +2485,3 @@ class TestRegression: assert arr.size * arr.itemsize > 2 ** 31 c_arr = np.ctypeslib.as_ctypes(arr) assert_equal(c_arr._length_, arr.size) - diff --git a/numpy/f2py/tests/util.py b/numpy/f2py/tests/util.py index 6dcc2ed12..c5b06697d 100644 --- a/numpy/f2py/tests/util.py +++ b/numpy/f2py/tests/util.py @@ -19,10 +19,7 @@ from numpy.compat import asbytes, asstr from numpy.testing import temppath from importlib import import_module -try: - from hashlib import md5 -except ImportError: - from md5 import new as md5 # noqa: F401 +from hashlib import md5 # # Maintaining a temporary module directory diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index b9f3bbb16..7211b68cf 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -4270,20 +4270,11 @@ def delete(arr, obj, axis=None): if axis is None: if ndim != 1: arr = arr.ravel() + # needed for np.matrix, which is still not 1d after being ravelled ndim = arr.ndim - axis = -1 - - if ndim == 0: - # 2013-09-24, 1.9 - warnings.warn( - "in the future the special handling of scalars will be removed " - "from delete and raise an error", DeprecationWarning, stacklevel=3) - if wrap: - return wrap(arr) - else: - return arr.copy(order=arrorder) - - axis = normalize_axis_index(axis, ndim) + axis = ndim - 1 + else: + axis = normalize_axis_index(axis, ndim) slobj = [slice(None)]*ndim N = arr.shape[axis] @@ -4344,6 +4335,7 @@ def delete(arr, obj, axis=None): # After removing the special handling of booleans and out of # bounds values, the conversion to the array can be removed. if obj.dtype == bool: + # 2012-10-11, NumPy 1.8 warnings.warn("in the future insert will treat boolean arrays and " "array-likes as boolean index instead of casting it " "to integer", FutureWarning, stacklevel=3) @@ -4368,32 +4360,8 @@ def delete(arr, obj, axis=None): else: if obj.size == 0 and not isinstance(_obj, np.ndarray): obj = obj.astype(intp) - if not np.can_cast(obj, intp, 'same_kind'): - # obj.size = 1 special case always failed and would just - # give superfluous warnings. - # 2013-09-24, 1.9 - warnings.warn( - "using a non-integer array as obj in delete will result in an " - "error in the future", DeprecationWarning, stacklevel=3) - obj = obj.astype(intp) keep = ones(N, dtype=bool) - # Test if there are out of bound indices, this is deprecated - inside_bounds = (obj < N) & (obj >= -N) - if not inside_bounds.all(): - # 2013-09-24, 1.9 - warnings.warn( - "in the future out of bounds indices will raise an error " - "instead of being ignored by `numpy.delete`.", - DeprecationWarning, stacklevel=3) - obj = obj[inside_bounds] - positive_indices = obj >= 0 - if not positive_indices.all(): - warnings.warn( - "in the future negative indices will not be ignored by " - "`numpy.delete`.", FutureWarning, stacklevel=3) - obj = obj[positive_indices] - keep[obj, ] = False slobj[axis] = keep new = arr[tuple(slobj)] @@ -4510,19 +4478,9 @@ def insert(arr, obj, values, axis=None): if axis is None: if ndim != 1: arr = arr.ravel() + # needed for np.matrix, which is still not 1d after being ravelled ndim = arr.ndim axis = ndim - 1 - elif ndim == 0: - # 2013-09-24, 1.9 - warnings.warn( - "in the future the special handling of scalars will be removed " - "from insert and raise an error", DeprecationWarning, stacklevel=3) - arr = arr.copy(order=arrorder) - arr[...] = values - if wrap: - return wrap(arr) - else: - return arr else: axis = normalize_axis_index(axis, ndim) slobj = [slice(None)]*ndim @@ -4531,12 +4489,13 @@ def insert(arr, obj, values, axis=None): if isinstance(obj, slice): # turn it into a range object - indices = arange(*obj.indices(N), **{'dtype': intp}) + indices = arange(*obj.indices(N), dtype=intp) else: # need to copy obj, because indices will be changed in-place indices = np.array(obj) if indices.dtype == bool: # See also delete + # 2012-10-11, NumPy 1.8 warnings.warn( "in the future insert will treat boolean arrays and " "array-likes as a boolean index instead of casting it to " @@ -4586,13 +4545,6 @@ def insert(arr, obj, values, axis=None): # Can safely cast the empty list to intp indices = indices.astype(intp) - if not np.can_cast(indices, intp, 'same_kind'): - # 2013-09-24, 1.9 - warnings.warn( - "using a non-integer array as obj in insert will result in an " - "error in the future", DeprecationWarning, stacklevel=3) - indices = indices.astype(intp) - indices[indices < 0] += N numnew = len(indices) diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index 860cf452b..fb10205d4 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -509,12 +509,11 @@ class TestInsert: insert(a, 1, a[:, 2, :], axis=1)) def test_0d(self): - # This is an error in the future a = np.array(1) - with warnings.catch_warnings(record=True) as w: - warnings.filterwarnings('always', '', DeprecationWarning) - assert_equal(insert(a, [], 2, axis=0), np.array(2)) - assert_(w[0].category is DeprecationWarning) + with pytest.raises(np.AxisError): + insert(a, [], 2, axis=0) + with pytest.raises(TypeError): + insert(a, [], 2, axis="nonsense") def test_subclass(self): class SubClass(np.ndarray): @@ -544,6 +543,12 @@ class TestInsert: b = np.insert(a, [0, 2], val) assert_array_equal(b[[0, 3]], np.array(val, dtype=b.dtype)) + def test_index_floats(self): + with pytest.raises(IndexError): + np.insert([0, 1, 2], np.array([1.0, 2.0]), [10, 20]) + with pytest.raises(IndexError): + np.insert([0, 1, 2], np.array([], dtype=float), []) + class TestAmax: @@ -807,7 +812,6 @@ class TestDelete: # NOTE: The cast should be removed after warning phase for bools if not isinstance(indices, (slice, int, long, np.integer)): indices = np.asarray(indices, dtype=np.intp) - indices = indices[(indices >= 0) & (indices < 5)] assert_array_equal(setxor1d(a_del, self.a[indices, ]), self.a, err_msg=msg) xor = setxor1d(nd_a_del[0,:, 0], self.nd_a[0, indices, 0]) @@ -825,15 +829,19 @@ class TestDelete: def test_fancy(self): # Deprecation/FutureWarning tests should be kept after change. self._check_inverse_of_slicing(np.array([[0, 1], [2, 1]])) - with warnings.catch_warnings(): - warnings.filterwarnings('error', category=DeprecationWarning) - assert_raises(DeprecationWarning, delete, self.a, [100]) - assert_raises(DeprecationWarning, delete, self.a, [-100]) + with pytest.raises(IndexError): + delete(self.a, [100]) + with pytest.raises(IndexError): + delete(self.a, [-100]) + + self._check_inverse_of_slicing([0, -1, 2, 2]) + with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always', category=FutureWarning) - self._check_inverse_of_slicing([0, -1, 2, 2]) obj = np.array([True, False, False], dtype=bool) self._check_inverse_of_slicing(obj) + # _check_inverse_of_slicing operates on two arrays, so warns twice + assert len(w) == 2 assert_(w[0].category is FutureWarning) assert_(w[1].category is FutureWarning) @@ -843,10 +851,10 @@ class TestDelete: def test_0d(self): a = np.array(1) - with warnings.catch_warnings(record=True) as w: - warnings.filterwarnings('always', '', DeprecationWarning) - assert_equal(delete(a, [], axis=0), a) - assert_(w[0].category is DeprecationWarning) + with pytest.raises(np.AxisError): + delete(a, [], axis=0) + with pytest.raises(TypeError): + delete(a, [], axis="nonsense") def test_subclass(self): class SubClass(np.ndarray): @@ -868,6 +876,12 @@ class TestDelete: assert_equal(m.flags.c_contiguous, k.flags.c_contiguous) assert_equal(m.flags.f_contiguous, k.flags.f_contiguous) + def test_index_floats(self): + with pytest.raises(IndexError): + np.delete([0, 1, 2], np.array([1.0, 2.0])) + with pytest.raises(IndexError): + np.delete([0, 1, 2], np.array([], dtype=float)) + class TestGradient: diff --git a/numpy/ma/core.py b/numpy/ma/core.py index e24cb956c..5c446252b 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -6385,6 +6385,21 @@ class MaskedConstant(MaskedArray): # it's a subclass, or something is wrong, make it obvious return object.__repr__(self) + def __format__(self, format_spec): + # Replace ndarray.__format__ with the default, which supports no format characters. + # Supporting format characters is unwise here, because we do not know what type + # the user was expecting - better to not guess. + try: + return object.__format__(self, format_spec) + except TypeError: + # 2020-03-23, NumPy 1.19.0 + warnings.warn( + "Format strings passed to MaskedConstant are ignored, but in future may " + "error or produce different behavior", + FutureWarning, stacklevel=2 + ) + return object.__format__(self, "") + def __reduce__(self): """Override of MaskedArray's __reduce__. """ diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 3bfb42555..98fc7dd97 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -447,6 +447,21 @@ class TestMaskedArray: assert_equal(copied.mask, [0, 0, 0]) assert_equal(a.mask, [0, 1, 0]) + def test_format(self): + a = array([0, 1, 2], mask=[False, True, False]) + assert_equal(format(a), "[0 -- 2]") + assert_equal(format(masked), "--") + assert_equal(format(masked, ""), "--") + + # Postponed from PR #15410, perhaps address in the future. + # assert_equal(format(masked, " >5"), " --") + # assert_equal(format(masked, " <5"), "-- ") + + # Expect a FutureWarning for using format_spec with MaskedElement + with assert_warns(FutureWarning): + with_format_string = format(masked, " >5") + assert_equal(with_format_string, "--") + def test_str_repr(self): a = array([0, 1, 2], mask=[False, True, False]) assert_equal(str(a), '[0 -- 2]') |