From eaca6853066c0cadb78917c998dbbcad94ed8b24 Mon Sep 17 00:00:00 2001 From: mattip Date: Mon, 12 Nov 2018 15:47:28 -0800 Subject: [PATCH] DEP: deprecate empty field names --- doc/release/1.16.0-notes.rst | 7 ++++++ numpy/core/src/multiarray/descriptor.c | 16 +++++++++++-- numpy/core/tests/test_deprecations.py | 10 +++++++++ numpy/lib/tests/test_format.py | 31 +++++++++++++------------- numpy/lib/tests/test_stride_tricks.py | 6 +++-- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/doc/release/1.16.0-notes.rst b/doc/release/1.16.0-notes.rst index 7f2a843eb452..70bd4a9686bd 100644 --- a/doc/release/1.16.0-notes.rst +++ b/doc/release/1.16.0-notes.rst @@ -48,6 +48,13 @@ As part of `NEP 15`, they have been deprecated along with the C-API functions the inner loop functions in built-in ufuncs should use :c:func:`PyUFunc_ReplaceLoopBySignature`. +Empty names in dtypes have been deprecated +------------------------------------------ +It has been possible to create a record array with an empty field name, i.e. +``np.dtype({'names': ['a', '', 'b'], 'formats': ['i4']*3})``. This conflicts +with internal usage of empty field names for padding, and unnamed fields cannot +be accessed by name. Such use has been deprecated. + Future Changes ============== diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index b9be3c09fad5..2435aa3c2064 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -1059,7 +1059,7 @@ _convert_from_dict(PyObject *obj, int align) totalsize = 0; for (i = 0; i < n; i++) { PyObject *tup, *descr, *ind, *title, *name, *off; - int len, ret, _align = 1; + int len, ret, tmp, _align = 1; PyArray_Descr *newdescr; /* Build item to insert (descr, offset, [title])*/ @@ -1169,7 +1169,19 @@ _convert_from_dict(PyObject *obj, int align) Py_DECREF(tup); goto fail; } - + tmp = PyObject_IsTrue(name); + if (tmp < 0) { + Py_DECREF(tup); + goto fail; + } + else if (tmp == 0) { + /* 2018-11-12 1.16 */ + if (DEPRECATE("Blank field names are deprecated and will result in " + "an error in the future.") < 0) { + Py_DECREF(tup); + goto fail; + } + } /* Insert into dictionary */ if (PyDict_GetItem(fields, name) != NULL) { PyErr_SetString(PyExc_ValueError, diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 7e6e256fe2d2..d9dbd29e87a6 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -519,11 +519,13 @@ class TestPositiveOnNonNumerical(_DeprecationTestCase): def test_positive_on_non_number(self): self.assert_deprecated(operator.pos, args=(np.array('foo'),)) + class TestFromstring(_DeprecationTestCase): # 2017-10-19, 1.14 def test_fromstring(self): self.assert_deprecated(np.fromstring, args=('\x00'*80,)) + class Test_GetSet_NumericOps(_DeprecationTestCase): # 2018-09-20, 1.16.0 def test_get_numeric_ops(self): @@ -534,3 +536,11 @@ def test_get_numeric_ops(self): # other tests. self.assert_deprecated(np.set_numeric_ops, kwargs={}) assert_raises(ValueError, np.set_numeric_ops, add='abc') + + +class TestDtypeEmptyFieldName(_DeprecationTestCase): + # 2018-11-12, 1.16 + def test_empty_fieldname(self): + d = {'names': ['a', '', 'b'], 'formats': ['i4']*3} + self.assert_deprecated(np.dtype, args=(d,)) + diff --git a/numpy/lib/tests/test_format.py b/numpy/lib/tests/test_format.py index 0d7c7f7e03a9..4443aa6028ea 100644 --- a/numpy/lib/tests/test_format.py +++ b/numpy/lib/tests/test_format.py @@ -287,7 +287,7 @@ import numpy as np from numpy.testing import ( assert_, assert_array_equal, assert_raises, assert_raises_regex, - raises + raises, suppress_warnings ) from numpy.lib import format @@ -525,27 +525,28 @@ def test_compressed_roundtrip(): # aligned -dt1 = np.dtype('i1, i4, i1', align=True) +dt1 = {'names': ['a', 'b', 'c'], 'formats': ['i1', 'i4', 'i1'], 'align': True} # non-aligned, explicit offsets -dt2 = np.dtype({'names': ['a', 'b'], 'formats': ['i4', 'i4'], - 'offsets': [1, 6]}) +dt2 = {'names': ['a', 'b'], 'formats': ['i4', 'i4'], 'offsets': [1, 6]} # nested struct-in-struct -dt3 = np.dtype({'names': ['c', 'd'], 'formats': ['i4', dt2]}) +dt3 = {'names': ['c', 'd'], 'formats': ['i4', dt2]} # field with '' name -dt4 = np.dtype({'names': ['a', '', 'b'], 'formats': ['i4']*3}) +dt4 = {'names': ['a', '', 'b'], 'formats': ['i4']*3} # titles -dt5 = np.dtype({'names': ['a', 'b'], 'formats': ['i4', 'i4'], - 'offsets': [1, 6], 'titles': ['aa', 'bb']}) +dt5 = {'names': ['a', 'b'], 'formats': ['i4', 'i4'], + 'offsets': [1, 6], 'titles': ['aa', 'bb']} @pytest.mark.parametrize("dt", [dt1, dt2, dt3, dt4, dt5]) def test_load_padded_dtype(dt): - arr = np.zeros(3, dt) - for i in range(3): - arr[i] = i + 5 - npz_file = os.path.join(tempdir, 'aligned.npz') - np.savez(npz_file, arr=arr) - arr1 = np.load(npz_file)['arr'] - assert_array_equal(arr, arr1) + with suppress_warnings('always') as sup: + sup.filter(DeprecationWarning) + arr = np.zeros(3, dtype=dt) + for i in range(3): + arr[i] = i + 5 + npz_file = os.path.join(tempdir, 'aligned.npz') + np.savez(npz_file, arr=arr) + arr1 = np.load(npz_file)['arr'] + assert_array_equal(arr, arr1) def test_python2_python3_interoperability(): diff --git a/numpy/lib/tests/test_stride_tricks.py b/numpy/lib/tests/test_stride_tricks.py index b2bd7da3ef47..9e7f7f65912a 100644 --- a/numpy/lib/tests/test_stride_tricks.py +++ b/numpy/lib/tests/test_stride_tricks.py @@ -4,7 +4,7 @@ from numpy.core._rational_tests import rational from numpy.testing import ( assert_equal, assert_array_equal, assert_raises, assert_, - assert_raises_regex + assert_raises_regex, suppress_warnings ) from numpy.lib.stride_tricks import ( as_strided, broadcast_arrays, _broadcast_shape, broadcast_to @@ -324,7 +324,9 @@ def test_as_strided(): assert_equal(a.dtype, a_view.dtype) # Make sure that the only type that could fail is properly handled - dt = np.dtype({'names': [''], 'formats': ['V4']}) + with suppress_warnings() as sup: + sup.filter(DeprecationWarning, '') + dt = np.dtype({'names': [''], 'formats': ['V4']}) a = np.empty((4,), dtype=dt) a_view = as_strided(a, shape=(3, 4), strides=(0, a.itemsize)) assert_equal(a.dtype, a_view.dtype)