summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release/1.9.0-notes.rst7
-rw-r--r--numpy/core/src/multiarray/array_assign_array.c4
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src6
-rw-r--r--numpy/core/src/multiarray/common.h2
-rw-r--r--numpy/core/src/multiarray/mapping.c231
-rw-r--r--numpy/core/src/multiarray/mapping.h2
-rw-r--r--numpy/core/src/multiarray/multiarray_tests.c.src33
-rw-r--r--numpy/core/src/multiarray/sequence.c6
-rw-r--r--numpy/core/tests/test_indexing.py53
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()