diff options
-rw-r--r-- | doc/release/1.9.0-notes.rst | 7 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_assign_array.c | 4 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 6 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common.h | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/mapping.c | 231 | ||||
-rw-r--r-- | numpy/core/src/multiarray/mapping.h | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/multiarray_tests.c.src | 33 | ||||
-rw-r--r-- | numpy/core/src/multiarray/sequence.c | 6 | ||||
-rw-r--r-- | numpy/core/tests/test_indexing.py | 53 |
9 files changed, 239 insertions, 105 deletions
diff --git a/doc/release/1.9.0-notes.rst b/doc/release/1.9.0-notes.rst index 48afc32c4..9550a2fcc 100644 --- a/doc/release/1.9.0-notes.rst +++ b/doc/release/1.9.0-notes.rst @@ -51,6 +51,13 @@ The unused ``simple_capsule_dtor`` function has been removed from ``npy_3kcompat.h``. Note that this header is not meant to be used outside of numpy; other projects should be using their own copy of this file when needed. +Negative indices in C-Api sq_item and sq_ass_item sequence methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When directly accessing the ``sq_item`` or ``sq_ass_item`` PyObject slots +for item getting, negative indices will not be supported anymore. +``PySequence_GetItem`` and ``PySequence_SetItem`` however fix negative +indices so that they can be used there. + New Features ============ diff --git a/numpy/core/src/multiarray/array_assign_array.c b/numpy/core/src/multiarray/array_assign_array.c index 1cf4b3a3b..ba18684a2 100644 --- a/numpy/core/src/multiarray/array_assign_array.c +++ b/numpy/core/src/multiarray/array_assign_array.c @@ -238,9 +238,9 @@ PyArray_AssignArray(PyArrayObject *dst, PyArrayObject *src, * case, first an in-place add is done, followed by an assignment, * equivalently expressed like this: * - * tmp = a[1000:6000] # Calls array_subscript_nice in mapping.c + * tmp = a[1000:6000] # Calls array_subscript in mapping.c * np.add(tmp, x, tmp) - * a[1000:6000] = tmp # Calls array_ass_sub in mapping.c + * a[1000:6000] = tmp # Calls array_assign_subscript in mapping.c * * In the assignment the underlying data type, shape, strides, and * data pointers are identical, but src != dst because they are separately diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 015609ed2..0f75c8aa5 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -3510,7 +3510,7 @@ static int /* We don't know what axis we're operating on, so don't report it in case of an error. */ if (check_and_adjust_index(&tmp, nindarray, -1) < 0) return 1; - if (nelem == 1) { + if (NPY_LIKELY(nelem == 1)) { *dest++ = *(src + tmp); } else { @@ -3536,7 +3536,7 @@ static int tmp -= nindarray; } } - if (nelem == 1) { + if (NPY_LIKELY(nelem == 1)) { *dest++ = *(src+tmp); } else { @@ -3558,7 +3558,7 @@ static int else if (tmp >= nindarray) { tmp = nindarray - 1; } - if (nelem == 1) { + if (NPY_LIKELY(nelem == 1)) { *dest++ = *(src + tmp); } else { diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index 43c6dc386..ff1d4ec77 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -75,7 +75,7 @@ static NPY_INLINE int check_and_adjust_index(npy_intp *index, npy_intp max_item, int axis) { /* Check that index is valid, taking into account negative indices */ - if ((*index < -max_item) || (*index >= max_item)) { + if (NPY_UNLIKELY((*index < -max_item) || (*index >= max_item))) { /* Try to be as clear as possible about what went wrong. */ if (axis >= 0) { PyErr_Format(PyExc_IndexError, diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c index 045c99007..056ca6133 100644 --- a/numpy/core/src/multiarray/mapping.c +++ b/numpy/core/src/multiarray/mapping.c @@ -54,82 +54,6 @@ array_length(PyArrayObject *self) } } -/* Get array item as scalar type */ -NPY_NO_EXPORT PyObject * -array_item_asscalar(PyArrayObject *self, npy_intp i) -{ - char *item; - npy_intp dim0; - - /* Bounds check and get the data pointer */ - dim0 = PyArray_DIM(self, 0); - if (i < 0) { - i += dim0; - } - if (i < 0 || i >= dim0) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return NULL; - } - item = PyArray_BYTES(self) + i * PyArray_STRIDE(self, 0); - return PyArray_Scalar(item, PyArray_DESCR(self), (PyObject *)self); -} - -/* Get array item as ndarray type */ -NPY_NO_EXPORT PyObject * -array_item_asarray(PyArrayObject *self, npy_intp i) -{ - char *item; - PyArrayObject *ret; - npy_intp dim0; - - if(PyArray_NDIM(self) == 0) { - PyErr_SetString(PyExc_IndexError, - "0-d arrays can't be indexed"); - return NULL; - } - - /* Bounds check and get the data pointer */ - dim0 = PyArray_DIM(self, 0); - if (check_and_adjust_index(&i, dim0, 0) < 0) { - return NULL; - } - item = PyArray_BYTES(self) + i * PyArray_STRIDE(self, 0); - - /* Create the view array */ - Py_INCREF(PyArray_DESCR(self)); - ret = (PyArrayObject *)PyArray_NewFromDescr(Py_TYPE(self), - PyArray_DESCR(self), - PyArray_NDIM(self)-1, - PyArray_DIMS(self)+1, - PyArray_STRIDES(self)+1, item, - PyArray_FLAGS(self), - (PyObject *)self); - if (ret == NULL) { - return NULL; - } - - /* Set the base object */ - Py_INCREF(self); - if (PyArray_SetBaseObject(ret, (PyObject *)self) < 0) { - Py_DECREF(ret); - return NULL; - } - - PyArray_UpdateFlags(ret, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS); - return (PyObject *)ret; -} - -/* Get array item at given index */ -NPY_NO_EXPORT PyObject * -array_item(PyArrayObject *self, Py_ssize_t i) -{ - if (PyArray_NDIM(self) == 1) { - return array_item_asscalar(self, (npy_intp) i); - } - else { - return array_item_asarray(self, (npy_intp) i); - } -} /* -------------------------------------------------------------- */ @@ -1144,7 +1068,7 @@ array_boolean_subscript(PyArrayObject *self, * Returns 0 on success, -1 on failure. */ NPY_NO_EXPORT int -array_ass_boolean_subscript(PyArrayObject *self, +array_assign_boolean_subscript(PyArrayObject *self, PyArrayObject *bmask, PyArrayObject *v, NPY_ORDER order) { npy_intp size, src_itemsize, v_stride; @@ -1297,6 +1221,74 @@ array_ass_boolean_subscript(PyArrayObject *self, } +/* + * C-level integer indexing always returning an array and never a scalar. + * Works also for subclasses, but it will not be called on one from the + * Python API. + * + * This function does not accept negative indices because it is called by + * PySequence_GetItem (through array_item) and that converts them to + * positive indices. + */ +NPY_NO_EXPORT PyObject * +array_item_asarray(PyArrayObject *self, npy_intp i) +{ + npy_index_info indices[2]; + PyObject *result; + + if (PyArray_NDIM(self) == 0) { + PyErr_SetString(PyExc_IndexError, + "too many indices for array"); + return NULL; + } + if (i < 0) { + /* This is an error, but undo PySequence_GetItem fix for message */ + i -= PyArray_DIM(self, 0); + } + + indices[0].value = i; + indices[0].type = HAS_INTEGER; + indices[1].value = PyArray_NDIM(self) - 1; + indices[1].type = HAS_ELLIPSIS; + if (get_view_from_index(self, (PyArrayObject **)&result, + indices, 2, 0) < 0) { + return NULL; + } + return result; +} + + +/* + * Python C-Api level item subscription (implementation for PySequence_GetItem) + * + * Negative indices are not accepted because PySequence_GetItem converts + * them to positive indices before calling this. + */ +NPY_NO_EXPORT PyObject * +array_item(PyArrayObject *self, Py_ssize_t i) +{ + if (PyArray_NDIM(self) == 1) { + char *item; + npy_index_info index; + + if (i < 0) { + /* This is an error, but undo PySequence_GetItem fix for message */ + i -= PyArray_DIM(self, 0); + } + + index.value = i; + index.type = HAS_INTEGER; + if (get_item_pointer(self, &item, &index, 1) < 0) { + return NULL; + } + return PyArray_Scalar(item, PyArray_DESCR(self), (PyObject *)self); + } + else { + return array_item_asarray(self, i); + } +} + + /* make sure subscript always returns an array object */ NPY_NO_EXPORT PyObject * array_subscript_asarray(PyArrayObject *self, PyObject *op) @@ -1304,6 +1296,10 @@ array_subscript_asarray(PyArrayObject *self, PyObject *op) return PyArray_EnsureAnyArray(array_subscript(self, op)); } + +/* + * General function for indexing a NumPy array with a Python object. + */ NPY_NO_EXPORT PyObject * array_subscript(PyArrayObject *self, PyObject *op) { @@ -1586,8 +1582,68 @@ array_subscript(PyArrayObject *self, PyObject *op) } +/* + * Python C-Api level item assignment (implementation for PySequence_SetItem) + * + * Negative indices are not accepted because PySequence_SetItem converts + * them to positive indices before calling this. + */ +NPY_NO_EXPORT int +array_assign_item(PyArrayObject *self, Py_ssize_t i, PyObject *op) +{ + npy_index_info indices[2]; + + if (op == NULL) { + PyErr_SetString(PyExc_ValueError, + "cannot delete array elements"); + return -1; + } + if (PyArray_FailUnlessWriteable(self, "assignment destination") < 0) { + return -1; + } + if (PyArray_NDIM(self) == 0) { + PyErr_SetString(PyExc_IndexError, + "too many indices for array"); + return -1; + } + + if (i < 0) { + /* This is an error, but undo PySequence_SetItem fix for message */ + i -= PyArray_DIM(self, 0); + } + + indices[0].value = i; + indices[0].type = HAS_INTEGER; + if (PyArray_NDIM(self) == 1) { + char *item; + if (get_item_pointer(self, &item, indices, 1) < 0) { + return -1; + } + if (PyArray_SETITEM(self, item, op) < 0) { + return -1; + } + } + else { + PyArrayObject *view; + + indices[1].value = PyArray_NDIM(self) - 1; + indices[1].type = HAS_ELLIPSIS; + if (get_view_from_index(self, &view, indices, 2, 0) < 0) { + return -1; + } + if (PyArray_CopyObject(view, op) < 0) { + return -1; + } + } + return 0; +} + + +/* + * General assignment with python indexing objects. + */ static int -array_ass_sub(PyArrayObject *self, PyObject *ind, PyObject *op) +array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op) { int index_type; int index_num; @@ -1674,9 +1730,9 @@ array_ass_sub(PyArrayObject *self, PyObject *ind, PyObject *op) Py_INCREF(op); tmp_arr = (PyArrayObject *)op; } - if (array_ass_boolean_subscript(self, - (PyArrayObject *)indices[0].object, - tmp_arr, NPY_CORDER) < 0) { + if (array_assign_boolean_subscript(self, + (PyArrayObject *)indices[0].object, + tmp_arr, NPY_CORDER) < 0) { goto fail; } goto success; @@ -1879,21 +1935,10 @@ array_ass_sub(PyArrayObject *self, PyObject *ind, PyObject *op) } -NPY_NO_EXPORT int -array_ass_item(PyArrayObject *self, Py_ssize_t i, PyObject *v) -{ - PyObject * ind = PyLong_FromSsize_t(i); - if (ind == NULL) { - return -1; - } - return array_ass_sub(self, ind, v); -} - - NPY_NO_EXPORT PyMappingMethods array_as_mapping = { (lenfunc)array_length, /*mp_length*/ (binaryfunc)array_subscript, /*mp_subscript*/ - (objobjargproc)array_ass_sub, /*mp_ass_subscript*/ + (objobjargproc)array_assign_subscript, /*mp_ass_subscript*/ }; /****************** End of Mapping Protocol ******************************/ diff --git a/numpy/core/src/multiarray/mapping.h b/numpy/core/src/multiarray/mapping.h index a1daf82ca..f59f6bb22 100644 --- a/numpy/core/src/multiarray/mapping.h +++ b/numpy/core/src/multiarray/mapping.h @@ -45,7 +45,7 @@ NPY_NO_EXPORT PyObject * array_subscript(PyArrayObject *self, PyObject *op); NPY_NO_EXPORT int -array_ass_item(PyArrayObject *self, Py_ssize_t i, PyObject *v); +array_assign_item(PyArrayObject *self, Py_ssize_t i, PyObject *v); /* * Prototypes for Mapping calls --- not part of the C-API diff --git a/numpy/core/src/multiarray/multiarray_tests.c.src b/numpy/core/src/multiarray/multiarray_tests.c.src index 9f327b24e..6285d0d82 100644 --- a/numpy/core/src/multiarray/multiarray_tests.c.src +++ b/numpy/core/src/multiarray/multiarray_tests.c.src @@ -690,6 +690,36 @@ get_buffer_info(PyObject *NPY_UNUSED(self), PyObject *args) #undef GET_PYBUF_FLAG +/* + * Test C-api level item getting. + */ +static PyObject * +array_indexing(PyObject *NPY_UNUSED(self), PyObject *args) +{ + int mode; + Py_ssize_t i; + PyObject *arr, *op = NULL; + + if (!PyArg_ParseTuple(args, "iOn|O", &mode, &arr, &i, &op)) { + return NULL; + } + + if (mode == 0) { + return PySequence_GetItem(arr, i); + } + if (mode == 1) { + if (PySequence_SetItem(arr, i, op) < 0) { + return NULL; + } + Py_RETURN_NONE; + } + + PyErr_SetString(PyExc_ValueError, + "invalid mode. 0: item 1: assign"); + return NULL; +} + + static PyMethodDef Multiarray_TestsMethods[] = { {"test_neighborhood_iterator", test_neighborhood_iterator, @@ -714,6 +744,9 @@ static PyMethodDef Multiarray_TestsMethods[] = { {"get_buffer_info", get_buffer_info, METH_VARARGS, NULL}, + {"array_indexing", + array_indexing, + METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/numpy/core/src/multiarray/sequence.c b/numpy/core/src/multiarray/sequence.c index 310c01084..520732acf 100644 --- a/numpy/core/src/multiarray/sequence.c +++ b/numpy/core/src/multiarray/sequence.c @@ -88,7 +88,7 @@ array_slice(PyArrayObject *self, Py_ssize_t ilow, Py_ssize_t ihigh) static int -array_ass_slice(PyArrayObject *self, Py_ssize_t ilow, +array_assign_slice(PyArrayObject *self, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { int ret; PyArrayObject *tmp; @@ -135,8 +135,8 @@ NPY_NO_EXPORT PySequenceMethods array_as_sequence = { (ssizeargfunc)NULL, (ssizeargfunc)array_item, (ssizessizeargfunc)array_slice, - (ssizeobjargproc)array_ass_item, /*sq_ass_item*/ - (ssizessizeobjargproc)array_ass_slice, /*sq_ass_slice*/ + (ssizeobjargproc)array_assign_item, /*sq_ass_item*/ + (ssizessizeobjargproc)array_assign_slice, /*sq_ass_slice*/ (objobjproc) array_contains, /*sq_contains */ (binaryfunc) NULL, /*sg_inplace_concat */ (ssizeargfunc)NULL, diff --git a/numpy/core/tests/test_indexing.py b/numpy/core/tests/test_indexing.py index c658dce09..20032bc28 100644 --- a/numpy/core/tests/test_indexing.py +++ b/numpy/core/tests/test_indexing.py @@ -1,10 +1,20 @@ from __future__ import division, absolute_import, print_function +import sys +import warnings +import functools + import numpy as np +from numpy.core.multiarray_tests import array_indexing from itertools import product -from numpy.compat import asbytes from numpy.testing import * -import sys, warnings, gc + + +try: + cdll = np.ctypeslib.load_library('multiarray', np.core.multiarray.__file__) + _HAS_CTYPE = True +except ImportError: + _HAS_CTYPE = False class TestIndexing(TestCase): @@ -861,5 +871,44 @@ class TestMultiIndexingAutomated(TestCase): self._check_single_index(a, index) +class TestCApiAccess(TestCase): + def test_getitem(self): + subscript = functools.partial(array_indexing, 0) + + # 0-d arrays don't work: + assert_raises(IndexError, subscript, np.ones(()), 0) + # Out of bound values: + assert_raises(IndexError, subscript, np.ones(10), 11) + assert_raises(IndexError, subscript, np.ones(10), -11) + assert_raises(IndexError, subscript, np.ones((10, 10)), 11) + assert_raises(IndexError, subscript, np.ones((10, 10)), -11) + + a = np.arange(10) + assert_array_equal(a[4], subscript(a, 4)) + a = a.reshape(5, 2) + assert_array_equal(a[-4], subscript(a, -4)) + + def test_setitem(self): + assign = functools.partial(array_indexing, 1) + + # Deletion is impossible: + assert_raises(ValueError, assign, np.ones(10), 0) + # 0-d arrays don't work: + assert_raises(IndexError, assign, np.ones(()), 0, 0) + # Out of bound values: + assert_raises(IndexError, assign, np.ones(10), 11, 0) + assert_raises(IndexError, assign, np.ones(10), -11, 0) + assert_raises(IndexError, assign, np.ones((10, 10)), 11, 0) + assert_raises(IndexError, assign, np.ones((10, 10)), -11, 0) + + a = np.arange(10) + assign(a, 4, 10) + assert_(a[4] == 10) + + a = a.reshape(5, 2) + assign(a, 4, 10) + assert_array_equal(a[-1], [10, 10]) + + if __name__ == "__main__": run_module_suite() |