Skip to content

gh-125420: implement Sequence.index API on memoryview objects #125446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4149,6 +4149,15 @@ copying.
.. versionchanged:: 3.5
The source format is no longer restricted when casting to a byte view.

.. method:: index(value, start=0, stop=sys.maxsize, /)

Return the index of the first occurrence of *value* (at or after
index *start* and before index *stop*).

Raises a :exc:`ValueError` if *value* cannot be found.

.. versionadded:: next

There are also several readonly attributes available:

.. attribute:: obj
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pickle
import struct

from itertools import product
from test.support import import_helper


Expand Down Expand Up @@ -58,6 +59,31 @@ def test_getitem(self):
for tp in self._types:
self.check_getitem_with_type(tp)

def test_index(self):
for tp in self._types:
b = tp(self._source)
m = self._view(b) # may be a sub-view
l = m.tolist()
k = 2 * len(self._source)

for chi in self._source:
if chi in l:
self.assertEqual(m.index(chi), l.index(chi))
else:
self.assertRaises(ValueError, m.index, chi)

for start, stop in product(range(-k, k), range(-k, k)):
index = -1
try:
index = l.index(chi, start, stop)
except ValueError:
pass

if index == -1:
self.assertRaises(ValueError, m.index, chi, start, stop)
else:
self.assertEqual(m.index(chi, start, stop), index)

def test_iter(self):
for tp in self._types:
b = tp(self._source)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :meth:`memoryview.index` to :class:`memoryview` objects. Patch by
Bénédikt Tran.
48 changes: 47 additions & 1 deletion Objects/clinic/memoryobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 87 additions & 0 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2748,6 +2748,92 @@ static PySequenceMethods memory_as_sequence = {
};


/**************************************************************************/
/* Lookup */
/**************************************************************************/

/*[clinic input]
memoryview.index

value: object
start: slice_index(accept={int}) = 0
stop: slice_index(accept={int}, c_default="PY_SSIZE_T_MAX") = sys.maxsize
/

Return the index of the first occurrence of a value.

Raises ValueError if the value is not present.
[clinic start generated code]*/

static PyObject *
memoryview_index_impl(PyMemoryViewObject *self, PyObject *value,
Py_ssize_t start, Py_ssize_t stop)
/*[clinic end generated code: output=e0185e3819e549df input=0697a0165bf90b5a]*/
{
const Py_buffer *view = &self->view;
CHECK_RELEASED(self);

if (view->ndim == 0) {
PyErr_SetString(PyExc_TypeError, "invalid lookup on 0-dim memory");
return NULL;
}

if (view->ndim == 1) {
Py_ssize_t n = view->shape[0];

if (start < 0) {
start = Py_MAX(start + n, 0);
}

if (stop < 0) {
stop = Py_MAX(stop + n, 0);
}

stop = Py_MIN(stop, n);
assert(stop >= 0);
assert(stop <= n);

start = Py_MIN(start, stop);
assert(0 <= start);
assert(start <= stop);

PyObject *obj = _PyObject_CAST(self);
for (Py_ssize_t index = start; index < stop; index++) {
// Note: while memoryviews can be mutated during iterations
// when calling the == operator, their shape cannot. As such,
// it is safe to assume that the index remains valid for the
// entire loop.
assert(index < n);

PyObject *item = memory_item(obj, index);
if (item == NULL) {
return NULL;
}
if (item == value) {
Py_DECREF(item);
return PyLong_FromSsize_t(index);
}
int contained = PyObject_RichCompareBool(item, value, Py_EQ);
Py_DECREF(item);
if (contained > 0) { // more likely than 'contained < 0'
return PyLong_FromSsize_t(index);
}
else if (contained < 0) {
return NULL;
}
}

PyErr_SetString(PyExc_ValueError, "memoryview.index(x): x not found");
return NULL;
}

PyErr_SetString(PyExc_NotImplementedError,
"multi-dimensional lookup is not implemented");
return NULL;

}


/**************************************************************************/
/* Comparisons */
/**************************************************************************/
Expand Down Expand Up @@ -3284,6 +3370,7 @@ static PyMethodDef memory_methods[] = {
MEMORYVIEW_CAST_METHODDEF
MEMORYVIEW_TOREADONLY_METHODDEF
MEMORYVIEW__FROM_FLAGS_METHODDEF
MEMORYVIEW_INDEX_METHODDEF
{"__enter__", memory_enter, METH_NOARGS, NULL},
{"__exit__", memory_exit, METH_VARARGS, memory_exit_doc},
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
Expand Down
Loading