Skip to content

Commit 58c7538

Browse files
authored
gh-125420: implement Sequence.index API on memoryview objects (#125446)
1 parent 3b18af9 commit 58c7538

File tree

5 files changed

+171
-1
lines changed

5 files changed

+171
-1
lines changed

Doc/library/stdtypes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,6 +4149,15 @@ copying.
41494149
.. versionchanged:: 3.5
41504150
The source format is no longer restricted when casting to a byte view.
41514151

4152+
.. method:: index(value, start=0, stop=sys.maxsize, /)
4153+
4154+
Return the index of the first occurrence of *value* (at or after
4155+
index *start* and before index *stop*).
4156+
4157+
Raises a :exc:`ValueError` if *value* cannot be found.
4158+
4159+
.. versionadded:: next
4160+
41524161
There are also several readonly attributes available:
41534162

41544163
.. attribute:: obj

Lib/test/test_memoryview.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pickle
1616
import struct
1717

18+
from itertools import product
1819
from test.support import import_helper
1920

2021

@@ -58,6 +59,31 @@ def test_getitem(self):
5859
for tp in self._types:
5960
self.check_getitem_with_type(tp)
6061

62+
def test_index(self):
63+
for tp in self._types:
64+
b = tp(self._source)
65+
m = self._view(b) # may be a sub-view
66+
l = m.tolist()
67+
k = 2 * len(self._source)
68+
69+
for chi in self._source:
70+
if chi in l:
71+
self.assertEqual(m.index(chi), l.index(chi))
72+
else:
73+
self.assertRaises(ValueError, m.index, chi)
74+
75+
for start, stop in product(range(-k, k), range(-k, k)):
76+
index = -1
77+
try:
78+
index = l.index(chi, start, stop)
79+
except ValueError:
80+
pass
81+
82+
if index == -1:
83+
self.assertRaises(ValueError, m.index, chi, start, stop)
84+
else:
85+
self.assertEqual(m.index(chi, start, stop), index)
86+
6187
def test_iter(self):
6288
for tp in self._types:
6389
b = tp(self._source)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :meth:`memoryview.index` to :class:`memoryview` objects. Patch by
2+
Bénédikt Tran.

Objects/clinic/memoryobject.c.h

Lines changed: 47 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/memoryobject.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2748,6 +2748,92 @@ static PySequenceMethods memory_as_sequence = {
27482748
};
27492749

27502750

2751+
/**************************************************************************/
2752+
/* Lookup */
2753+
/**************************************************************************/
2754+
2755+
/*[clinic input]
2756+
memoryview.index
2757+
2758+
value: object
2759+
start: slice_index(accept={int}) = 0
2760+
stop: slice_index(accept={int}, c_default="PY_SSIZE_T_MAX") = sys.maxsize
2761+
/
2762+
2763+
Return the index of the first occurrence of a value.
2764+
2765+
Raises ValueError if the value is not present.
2766+
[clinic start generated code]*/
2767+
2768+
static PyObject *
2769+
memoryview_index_impl(PyMemoryViewObject *self, PyObject *value,
2770+
Py_ssize_t start, Py_ssize_t stop)
2771+
/*[clinic end generated code: output=e0185e3819e549df input=0697a0165bf90b5a]*/
2772+
{
2773+
const Py_buffer *view = &self->view;
2774+
CHECK_RELEASED(self);
2775+
2776+
if (view->ndim == 0) {
2777+
PyErr_SetString(PyExc_TypeError, "invalid lookup on 0-dim memory");
2778+
return NULL;
2779+
}
2780+
2781+
if (view->ndim == 1) {
2782+
Py_ssize_t n = view->shape[0];
2783+
2784+
if (start < 0) {
2785+
start = Py_MAX(start + n, 0);
2786+
}
2787+
2788+
if (stop < 0) {
2789+
stop = Py_MAX(stop + n, 0);
2790+
}
2791+
2792+
stop = Py_MIN(stop, n);
2793+
assert(stop >= 0);
2794+
assert(stop <= n);
2795+
2796+
start = Py_MIN(start, stop);
2797+
assert(0 <= start);
2798+
assert(start <= stop);
2799+
2800+
PyObject *obj = _PyObject_CAST(self);
2801+
for (Py_ssize_t index = start; index < stop; index++) {
2802+
// Note: while memoryviews can be mutated during iterations
2803+
// when calling the == operator, their shape cannot. As such,
2804+
// it is safe to assume that the index remains valid for the
2805+
// entire loop.
2806+
assert(index < n);
2807+
2808+
PyObject *item = memory_item(obj, index);
2809+
if (item == NULL) {
2810+
return NULL;
2811+
}
2812+
if (item == value) {
2813+
Py_DECREF(item);
2814+
return PyLong_FromSsize_t(index);
2815+
}
2816+
int contained = PyObject_RichCompareBool(item, value, Py_EQ);
2817+
Py_DECREF(item);
2818+
if (contained > 0) { // more likely than 'contained < 0'
2819+
return PyLong_FromSsize_t(index);
2820+
}
2821+
else if (contained < 0) {
2822+
return NULL;
2823+
}
2824+
}
2825+
2826+
PyErr_SetString(PyExc_ValueError, "memoryview.index(x): x not found");
2827+
return NULL;
2828+
}
2829+
2830+
PyErr_SetString(PyExc_NotImplementedError,
2831+
"multi-dimensional lookup is not implemented");
2832+
return NULL;
2833+
2834+
}
2835+
2836+
27512837
/**************************************************************************/
27522838
/* Comparisons */
27532839
/**************************************************************************/
@@ -3284,6 +3370,7 @@ static PyMethodDef memory_methods[] = {
32843370
MEMORYVIEW_CAST_METHODDEF
32853371
MEMORYVIEW_TOREADONLY_METHODDEF
32863372
MEMORYVIEW__FROM_FLAGS_METHODDEF
3373+
MEMORYVIEW_INDEX_METHODDEF
32873374
{"__enter__", memory_enter, METH_NOARGS, NULL},
32883375
{"__exit__", memory_exit, METH_VARARGS, memory_exit_doc},
32893376
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},

0 commit comments

Comments
 (0)