summaryrefslogtreecommitdiff
path: root/numpy/core
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/core')
-rw-r--r--numpy/core/src/multiarray/common.c2
-rw-r--r--numpy/core/src/multiarray/convert.c8
-rw-r--r--numpy/core/src/multiarray/mapping.c60
-rw-r--r--numpy/core/src/private/npy_import.h14
-rw-r--r--numpy/core/tests/test_multiarray.py67
-rw-r--r--numpy/core/tests/test_records.py2
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", &copyfunc);
+ 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']]