Skip to content

Commit 4331832

Browse files
authored
gh-125420: implement Sequence.count API on memoryview objects (#125443)
1 parent 050d59b commit 4331832

File tree

5 files changed

+97
-2
lines changed

5 files changed

+97
-2
lines changed

Doc/library/stdtypes.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4149,7 +4149,13 @@ 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, /)
4152+
.. method:: count(value, /)
4153+
4154+
Count the number of occurrences of *value*.
4155+
4156+
.. versionadded:: next
4157+
4158+
.. method:: index(value, start=0, stop=sys.maxsize, /)
41534159

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

Lib/test/test_memoryview.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ def test_iter(self):
9090
m = self._view(b)
9191
self.assertEqual(list(m), [m[i] for i in range(len(m))])
9292

93+
def test_count(self):
94+
for tp in self._types:
95+
b = tp(self._source)
96+
m = self._view(b)
97+
l = m.tolist()
98+
for ch in list(m):
99+
self.assertEqual(m.count(ch), l.count(ch))
100+
101+
b = tp((b'a' * 5) + (b'c' * 3))
102+
m = self._view(b) # may be sliced
103+
l = m.tolist()
104+
with self.subTest('count', buffer=b):
105+
self.assertEqual(m.count(ord('a')), l.count(ord('a')))
106+
self.assertEqual(m.count(ord('b')), l.count(ord('b')))
107+
self.assertEqual(m.count(ord('c')), l.count(ord('c')))
108+
93109
def test_setitem_readonly(self):
94110
if not self.ro_type:
95111
self.skipTest("no read-only type to test")
@@ -464,6 +480,18 @@ def _view(self, obj):
464480
def _check_contents(self, tp, obj, contents):
465481
self.assertEqual(obj, tp(contents))
466482

483+
def test_count(self):
484+
super().test_count()
485+
for tp in self._types:
486+
b = tp((b'a' * 5) + (b'c' * 3))
487+
m = self._view(b) # should not be sliced
488+
self.assertEqual(len(b), len(m))
489+
with self.subTest('count', buffer=b):
490+
self.assertEqual(m.count(ord('a')), 5)
491+
self.assertEqual(m.count(ord('b')), 0)
492+
self.assertEqual(m.count(ord('c')), 3)
493+
494+
467495
class BaseMemorySliceTests:
468496
source_bytes = b"XabcdefY"
469497

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :meth:`memoryview.count` to :class:`memoryview` objects. Patch by
2+
Bénédikt Tran.

Objects/clinic/memoryobject.c.h

Lines changed: 10 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: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2748,6 +2748,55 @@ static PySequenceMethods memory_as_sequence = {
27482748
};
27492749

27502750

2751+
/****************************************************************************/
2752+
/* Counting */
2753+
/****************************************************************************/
2754+
2755+
/*[clinic input]
2756+
memoryview.count
2757+
2758+
value: object
2759+
/
2760+
2761+
Count the number of occurrences of a value.
2762+
[clinic start generated code]*/
2763+
2764+
static PyObject *
2765+
memoryview_count(PyMemoryViewObject *self, PyObject *value)
2766+
/*[clinic end generated code: output=e2c255a8d54eaa12 input=e3036ce1ed7d1823]*/
2767+
{
2768+
PyObject *iter = PyObject_GetIter(_PyObject_CAST(self));
2769+
if (iter == NULL) {
2770+
return NULL;
2771+
}
2772+
2773+
Py_ssize_t count = 0;
2774+
PyObject *item = NULL;
2775+
while (PyIter_NextItem(iter, &item)) {
2776+
if (item == NULL) {
2777+
Py_DECREF(iter);
2778+
return NULL;
2779+
}
2780+
if (item == value) {
2781+
Py_DECREF(item);
2782+
count++; // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
2783+
continue;
2784+
}
2785+
int contained = PyObject_RichCompareBool(item, value, Py_EQ);
2786+
Py_DECREF(item);
2787+
if (contained > 0) { // more likely than 'contained < 0'
2788+
count++; // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
2789+
}
2790+
else if (contained < 0) {
2791+
Py_DECREF(iter);
2792+
return NULL;
2793+
}
2794+
}
2795+
Py_DECREF(iter);
2796+
return PyLong_FromSsize_t(count);
2797+
}
2798+
2799+
27512800
/**************************************************************************/
27522801
/* Lookup */
27532802
/**************************************************************************/
@@ -3370,6 +3419,7 @@ static PyMethodDef memory_methods[] = {
33703419
MEMORYVIEW_CAST_METHODDEF
33713420
MEMORYVIEW_TOREADONLY_METHODDEF
33723421
MEMORYVIEW__FROM_FLAGS_METHODDEF
3422+
MEMORYVIEW_COUNT_METHODDEF
33733423
MEMORYVIEW_INDEX_METHODDEF
33743424
{"__enter__", memory_enter, METH_NOARGS, NULL},
33753425
{"__exit__", memory_exit, METH_VARARGS, memory_exit_doc},

0 commit comments

Comments
 (0)