diff options
Diffstat (limited to 'numpy/core')
-rw-r--r-- | numpy/core/src/multiarray/common.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert.c | 8 | ||||
-rw-r--r-- | numpy/core/src/multiarray/mapping.c | 60 | ||||
-rw-r--r-- | numpy/core/src/private/npy_import.h | 14 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 67 | ||||
-rw-r--r-- | numpy/core/tests/test_records.py | 2 |
6 files changed, 139 insertions, 14 deletions
diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index f191f8db4..c70f8526e 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -424,7 +424,7 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims, * __len__ is not defined. */ if (maxdims == 0 || !PySequence_Check(obj) || PySequence_Size(obj) < 0) { - // clear any PySequence_Size error, which corrupts further calls to it + /* clear any PySequence_Size error which corrupts further calls */ PyErr_Clear(); if (*out_dtype == NULL || (*out_dtype)->type_num != NPY_OBJECT) { diff --git a/numpy/core/src/multiarray/convert.c b/numpy/core/src/multiarray/convert.c index 0e38aaa61..e88582a51 100644 --- a/numpy/core/src/multiarray/convert.c +++ b/numpy/core/src/multiarray/convert.c @@ -613,11 +613,14 @@ PyArray_View(PyArrayObject *self, PyArray_Descr *type, PyTypeObject *pytype) subtype = Py_TYPE(self); } - if (type != NULL && (PyArray_FLAGS(self) & NPY_ARRAY_WARN_ON_WRITE)) { + dtype = PyArray_DESCR(self); + + if (type != NULL && !PyArray_EquivTypes(dtype, type) && + (PyArray_FLAGS(self) & NPY_ARRAY_WARN_ON_WRITE)) { const char *msg = "Numpy has detected that you may be viewing or writing to an array " "returned by selecting multiple fields in a structured array. \n\n" - "This code may break in numpy 1.13 because this will return a view " + "This code may break in numpy 1.16 because this will return a view " "instead of a copy -- see release notes for details."; /* 2016-09-19, 1.12 */ if (DEPRECATE_FUTUREWARNING(msg) < 0) { @@ -629,7 +632,6 @@ PyArray_View(PyArrayObject *self, PyArray_Descr *type, PyTypeObject *pytype) flags = PyArray_FLAGS(self); - dtype = PyArray_DESCR(self); Py_INCREF(dtype); ret = (PyArrayObject *)PyArray_NewFromDescr_int( subtype, dtype, diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c index 6a7ffd39d..82a0683f9 100644 --- a/numpy/core/src/multiarray/mapping.c +++ b/numpy/core/src/multiarray/mapping.c @@ -1387,14 +1387,54 @@ array_subscript_asarray(PyArrayObject *self, PyObject *op) } /* + * Helper function for _get_field_view which turns a multifield + * view into a "packed" copy, as done in numpy 1.15 and before. + * In numpy 1.16 this function should be removed. + */ +NPY_NO_EXPORT int +_multifield_view_to_copy(PyArrayObject **view) { + static PyObject *copyfunc = NULL; + PyObject *viewcopy; + + /* return a repacked copy of the view */ + npy_cache_import("numpy.lib.recfunctions", "repack_fields", ©func); + if (copyfunc == NULL) { + goto view_fail; + } + + PyArray_CLEARFLAGS(*view, NPY_ARRAY_WARN_ON_WRITE); + viewcopy = PyObject_CallFunction(copyfunc, "O", *view); + if (viewcopy == NULL) { + goto view_fail; + } + Py_DECREF(*view); + *view = (PyArrayObject*)viewcopy; + + /* warn when writing to the copy */ + PyArray_ENABLEFLAGS(*view, NPY_ARRAY_WARN_ON_WRITE); + return 0; + +view_fail: + Py_DECREF(*view); + *view = NULL; + return 0; +} + +/* * Attempts to subscript an array using a field name or list of field names. * * If an error occurred, return 0 and set view to NULL. If the subscript is not * a string or list of strings, return -1 and set view to NULL. Otherwise * return 0 and set view to point to a new view into arr for the given fields. + * + * In numpy 1.15 and before, in the case of a list of field names the returned + * view will actually be a copy by default, with fields packed together. + * The `force_view` argument causes a view to be returned. This argument can be + * removed in 1.16 when we plan to return a view always. */ NPY_NO_EXPORT int -_get_field_view(PyArrayObject *arr, PyObject *ind, PyArrayObject **view) +_get_field_view(PyArrayObject *arr, PyObject *ind, PyArrayObject **view, + int force_view) { *view = NULL; @@ -1489,12 +1529,12 @@ _get_field_view(PyArrayObject *arr, PyObject *ind, PyArrayObject **view) Py_DECREF(names); return 0; } - // disallow use of titles as index + /* disallow use of titles as index */ if (PyTuple_Size(tup) == 3) { PyObject *title = PyTuple_GET_ITEM(tup, 2); int titlecmp = PyObject_RichCompareBool(title, name, Py_EQ); if (titlecmp == 1) { - // if title == name, we were given a title, not a field name + /* if title == name, we got a title, not a field name */ PyErr_SetString(PyExc_KeyError, "cannot use field titles in multi-field index"); } @@ -1507,7 +1547,7 @@ _get_field_view(PyArrayObject *arr, PyObject *ind, PyArrayObject **view) } Py_DECREF(title); } - // disallow duplicate field indices + /* disallow duplicate field indices */ if (PyDict_Contains(fields, name)) { PyObject *errmsg = PyUString_FromString( "duplicate field of name "); @@ -1552,10 +1592,16 @@ _get_field_view(PyArrayObject *arr, PyObject *ind, PyArrayObject **view) PyArray_FLAGS(arr), (PyObject *)arr, (PyObject *)arr, 0, 1); + if (*view == NULL) { return 0; } - return 0; + + /* the code below can be replaced by "return 0" in 1.16 */ + if (force_view) { + return 0; + } + return _multifield_view_to_copy(view); } return -1; } @@ -1583,7 +1629,7 @@ array_subscript(PyArrayObject *self, PyObject *op) /* return fields if op is a string index */ if (PyDataType_HASFIELDS(PyArray_DESCR(self))) { PyArrayObject *view; - int ret = _get_field_view(self, op, &view); + int ret = _get_field_view(self, op, &view, 0); if (ret == 0){ if (view == NULL) { return NULL; @@ -1865,7 +1911,7 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op) /* field access */ if (PyDataType_HASFIELDS(PyArray_DESCR(self))){ PyArrayObject *view; - int ret = _get_field_view(self, ind, &view); + int ret = _get_field_view(self, ind, &view, 1); if (ret == 0){ if (view == NULL) { return -1; diff --git a/numpy/core/src/private/npy_import.h b/numpy/core/src/private/npy_import.h index 221e1e645..e145a843e 100644 --- a/numpy/core/src/private/npy_import.h +++ b/numpy/core/src/private/npy_import.h @@ -29,4 +29,18 @@ npy_cache_import(const char *module, const char *attr, PyObject **cache) } } +NPY_INLINE static PyObject * +npy_import(const char *module, const char *attr) +{ + PyObject *mod = PyImport_ImportModule(module); + PyObject *ret = NULL; + + if (mod != NULL) { + ret = PyObject_GetAttrString(mod, attr); + } + Py_XDECREF(mod); + + return ret; +} + #endif diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index b13e48fdc..37d73e42c 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -4796,9 +4796,25 @@ class TestRecord(object): fn2 = func('f2') b[fn2] = 3 - assert_equal(b[['f1', 'f2']][0].tolist(), (2, 3)) - assert_equal(b[['f2', 'f1']][0].tolist(), (3, 2)) - assert_equal(b[['f1', 'f3']][0].tolist(), (2, (1,))) + # In 1.16 code below can be replaced by: + # assert_equal(b[['f1', 'f2']][0].tolist(), (2, 3)) + # assert_equal(b[['f2', 'f1']][0].tolist(), (3, 2)) + # assert_equal(b[['f1', 'f3']][0].tolist(), (2, (1,))) + with suppress_warnings() as sup: + sup.filter(FutureWarning, + ".* selecting multiple fields .*") + + assert_equal(b[['f1', 'f2']][0].tolist(), (2, 3)) + assert_equal(b[['f2', 'f1']][0].tolist(), (3, 2)) + assert_equal(b[['f1', 'f3']][0].tolist(), (2, (1,))) + # view of subfield view/copy + assert_equal(b[['f1', 'f2']][0].view(('i4', 2)).tolist(), + (2, 3)) + assert_equal(b[['f2', 'f1']][0].view(('i4', 2)).tolist(), + (3, 2)) + view_dtype = [('f1', 'i4'), ('f3', [('', 'i4')])] + assert_equal(b[['f1', 'f3']][0].view(view_dtype).tolist(), + (2, (1,))) # non-ascii unicode field indexing is well behaved if not is_py3: @@ -4808,6 +4824,51 @@ class TestRecord(object): assert_raises(ValueError, a.__setitem__, u'\u03e0', 1) assert_raises(ValueError, a.__getitem__, u'\u03e0') + # can be removed in 1.16 + def test_field_names_deprecation(self): + + def collect_warnings(f, *args, **kwargs): + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always") + f(*args, **kwargs) + return [w.category for w in log] + + a = np.zeros((1,), dtype=[('f1', 'i4'), + ('f2', 'i4'), + ('f3', [('sf1', 'i4')])]) + a['f1'][0] = 1 + a['f2'][0] = 2 + a['f3'][0] = (3,) + b = np.zeros((1,), dtype=[('f1', 'i4'), + ('f2', 'i4'), + ('f3', [('sf1', 'i4')])]) + b['f1'][0] = 1 + b['f2'][0] = 2 + b['f3'][0] = (3,) + + # All the different functions raise a warning, but not an error + assert_equal(collect_warnings(a[['f1', 'f2']].__setitem__, 0, (10, 20)), + [FutureWarning]) + # For <=1.12 a is not modified, but it will be in 1.13 + assert_equal(a, b) + + # Views also warn + subset = a[['f1', 'f2']] + subset_view = subset.view() + assert_equal(collect_warnings(subset_view['f1'].__setitem__, 0, 10), + [FutureWarning]) + # But the write goes through: + assert_equal(subset['f1'][0], 10) + # Only one warning per multiple field indexing, though (even if there + # are multiple views involved): + assert_equal(collect_warnings(subset['f1'].__setitem__, 0, 10), []) + + # make sure views of a multi-field index warn too + c = np.zeros(3, dtype='i8,i8,i8') + assert_equal(collect_warnings(c[['f0', 'f2']].view, 'i8,i8'), + [FutureWarning]) + + def test_record_hash(self): a = np.array([(1, 2), (1, 2)], dtype='i1,i2') a.flags.writeable = False diff --git a/numpy/core/tests/test_records.py b/numpy/core/tests/test_records.py index 9aeb93f74..d7c7d16e3 100644 --- a/numpy/core/tests/test_records.py +++ b/numpy/core/tests/test_records.py @@ -11,6 +11,7 @@ import pickle import warnings import textwrap from os import path +import pytest import numpy as np from numpy.testing import ( @@ -360,6 +361,7 @@ class TestRecord(object): with assert_raises(ValueError): r.setfield([2,3], *r.dtype.fields['f']) + @pytest.mark.xfail(reason="See gh-10411, becomes real error in 1.16") def test_out_of_order_fields(self): # names in the same order, padding added to descr x = self.data[['col1', 'col2']] |