summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>2024-12-10 10:12:33 (GMT)
committerGitHub <noreply@github.com>2024-12-10 10:12:33 (GMT)
commit4331832db02ff4a7598dcdd99cae31087173dce0 (patch)
tree1a0b95b73e42dc9bb23babc36130acddabb942cb
parent050d59bd1765de417bf4ec8b5c3cbdac65695f5e (diff)
downloadcpython-4331832db02ff4a7598dcdd99cae31087173dce0.zip
cpython-4331832db02ff4a7598dcdd99cae31087173dce0.tar.gz
cpython-4331832db02ff4a7598dcdd99cae31087173dce0.tar.bz2
gh-125420: implement `Sequence.count` API on `memoryview` objects (#125443)
-rw-r--r--Doc/library/stdtypes.rst8
-rw-r--r--Lib/test/test_memoryview.py28
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst2
-rw-r--r--Objects/clinic/memoryobject.c.h11
-rw-r--r--Objects/memoryobject.c50
5 files changed, 97 insertions, 2 deletions
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index f4e635d..d5f7714 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -4149,7 +4149,13 @@ 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, /)
+ .. method:: count(value, /)
+
+ Count the number of occurrences of *value*.
+
+ .. versionadded:: next
+
+ .. 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*).
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index 8f07828..96320eb 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -90,6 +90,22 @@ class AbstractMemoryTests:
m = self._view(b)
self.assertEqual(list(m), [m[i] for i in range(len(m))])
+ def test_count(self):
+ for tp in self._types:
+ b = tp(self._source)
+ m = self._view(b)
+ l = m.tolist()
+ for ch in list(m):
+ self.assertEqual(m.count(ch), l.count(ch))
+
+ b = tp((b'a' * 5) + (b'c' * 3))
+ m = self._view(b) # may be sliced
+ l = m.tolist()
+ with self.subTest('count', buffer=b):
+ self.assertEqual(m.count(ord('a')), l.count(ord('a')))
+ self.assertEqual(m.count(ord('b')), l.count(ord('b')))
+ self.assertEqual(m.count(ord('c')), l.count(ord('c')))
+
def test_setitem_readonly(self):
if not self.ro_type:
self.skipTest("no read-only type to test")
@@ -464,6 +480,18 @@ class BaseMemoryviewTests:
def _check_contents(self, tp, obj, contents):
self.assertEqual(obj, tp(contents))
+ def test_count(self):
+ super().test_count()
+ for tp in self._types:
+ b = tp((b'a' * 5) + (b'c' * 3))
+ m = self._view(b) # should not be sliced
+ self.assertEqual(len(b), len(m))
+ with self.subTest('count', buffer=b):
+ self.assertEqual(m.count(ord('a')), 5)
+ self.assertEqual(m.count(ord('b')), 0)
+ self.assertEqual(m.count(ord('c')), 3)
+
+
class BaseMemorySliceTests:
source_bytes = b"XabcdefY"
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst
new file mode 100644
index 0000000..ef12080
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst
@@ -0,0 +1,2 @@
+Add :meth:`memoryview.count` to :class:`memoryview` objects. Patch by
+Bénédikt Tran.
diff --git a/Objects/clinic/memoryobject.c.h b/Objects/clinic/memoryobject.c.h
index 9b8a233..a6cf1f4 100644
--- a/Objects/clinic/memoryobject.c.h
+++ b/Objects/clinic/memoryobject.c.h
@@ -419,6 +419,15 @@ exit:
return return_value;
}
+PyDoc_STRVAR(memoryview_count__doc__,
+"count($self, value, /)\n"
+"--\n"
+"\n"
+"Count the number of occurrences of a value.");
+
+#define MEMORYVIEW_COUNT_METHODDEF \
+ {"count", (PyCFunction)memoryview_count, METH_O, memoryview_count__doc__},
+
PyDoc_STRVAR(memoryview_index__doc__,
"index($self, value, start=0, stop=sys.maxsize, /)\n"
"--\n"
@@ -464,4 +473,4 @@ skip_optional:
exit:
return return_value;
}
-/*[clinic end generated code: output=2742d371dba7314f input=a9049054013a1b77]*/
+/*[clinic end generated code: output=132893ef5f67ad73 input=a9049054013a1b77]*/
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index 345aa79..afe2dcb 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -2748,6 +2748,55 @@ static PySequenceMethods memory_as_sequence = {
};
+/****************************************************************************/
+/* Counting */
+/****************************************************************************/
+
+/*[clinic input]
+memoryview.count
+
+ value: object
+ /
+
+Count the number of occurrences of a value.
+[clinic start generated code]*/
+
+static PyObject *
+memoryview_count(PyMemoryViewObject *self, PyObject *value)
+/*[clinic end generated code: output=e2c255a8d54eaa12 input=e3036ce1ed7d1823]*/
+{
+ PyObject *iter = PyObject_GetIter(_PyObject_CAST(self));
+ if (iter == NULL) {
+ return NULL;
+ }
+
+ Py_ssize_t count = 0;
+ PyObject *item = NULL;
+ while (PyIter_NextItem(iter, &item)) {
+ if (item == NULL) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ if (item == value) {
+ Py_DECREF(item);
+ count++; // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
+ continue;
+ }
+ int contained = PyObject_RichCompareBool(item, value, Py_EQ);
+ Py_DECREF(item);
+ if (contained > 0) { // more likely than 'contained < 0'
+ count++; // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
+ }
+ else if (contained < 0) {
+ Py_DECREF(iter);
+ return NULL;
+ }
+ }
+ Py_DECREF(iter);
+ return PyLong_FromSsize_t(count);
+}
+
+
/**************************************************************************/
/* Lookup */
/**************************************************************************/
@@ -3370,6 +3419,7 @@ static PyMethodDef memory_methods[] = {
MEMORYVIEW_CAST_METHODDEF
MEMORYVIEW_TOREADONLY_METHODDEF
MEMORYVIEW__FROM_FLAGS_METHODDEF
+ MEMORYVIEW_COUNT_METHODDEF
MEMORYVIEW_INDEX_METHODDEF
{"__enter__", memory_enter, METH_NOARGS, NULL},
{"__exit__", memory_exit, METH_VARARGS, memory_exit_doc},