Skip to content
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

gh-125420: implement Sequence.__reversed__ API on memoryview objects #125505

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def _f(): pass
dict_itemiterator = type(iter({}.items()))
list_iterator = type(iter([]))
list_reverseiterator = type(iter(reversed([])))
memory_iterator = type(iter(memoryview(b'')))
memory_reverseiterator = type(reversed(memoryview(b'')))
range_iterator = type(iter(range(0)))
longrange_iterator = type(iter(range(1 << 1000)))
set_iterator = type(iter(set()))
Expand Down Expand Up @@ -325,6 +327,8 @@ def __subclasshook__(cls, C):
Iterator.register(dict_itemiterator)
Iterator.register(list_iterator)
Iterator.register(list_reverseiterator)
Iterator.register(memory_iterator)
Iterator.register(memory_reverseiterator)
Iterator.register(range_iterator)
Iterator.register(longrange_iterator)
Iterator.register(set_iterator)
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,10 @@ def test_reversed(self):
b = tp(self._source)
m = self._view(b)
aslist = list(reversed(m.tolist()))
self.assertEqual(list(reversed(m)), aslist)
self.assertEqual(list(reversed(m)), list(m[::-1]))
with self.subTest(view_type=type(m)):
self.assertTrue(hasattr(m, '__reversed__'))
self.assertEqual(list(reversed(m)), aslist)
self.assertEqual(list(reversed(m)), list(m[::-1]))

def test_toreadonly(self):
for tp in self._types:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :meth:`~object.__reversed__` to :class:`memoryview` objects. Patch by
Bénédikt Tran.
20 changes: 19 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.

159 changes: 110 additions & 49 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,7 @@ static PyMethodDef memory_methods[] = {
MEMORYVIEW__FROM_FLAGS_METHODDEF
{"__enter__", memory_enter, METH_NOARGS, NULL},
{"__exit__", memory_exit, METH_VARARGS, memory_exit_doc},
MEMORYVIEW___REVERSED___METHODDEF
{NULL, NULL}
};

Expand All @@ -3294,6 +3295,7 @@ static PyMethodDef memory_methods[] = {
/**************************************************************************/

PyTypeObject _PyMemoryIter_Type;
PyTypeObject _PyMemoryRevIter_Type;

typedef struct {
PyObject_HEAD
Expand All @@ -3303,10 +3305,51 @@ typedef struct {
const char *it_fmt;
} memoryiterobject;

#define _PyMemoryViewIter_CAST(PTR) ((memoryiterobject *)(PTR))

static PyObject *
memoryiter_new(PyObject *self, int reversed)
{
if (!PyMemoryView_Check(self)) {
PyErr_BadInternalCall();
return NULL;
}

PyMemoryViewObject *sequence = _PyMemoryView_CAST(self);
const Py_buffer *view = &sequence->view;
if (view->ndim == 0) {
PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory");
return NULL;
}
else if (view->ndim != 1) {
PyErr_SetString(PyExc_NotImplementedError,
"multi-dimensional sub-views are not implemented");
return NULL;
}
const char *fmt = adjust_fmt(view);
if (fmt == NULL) {
return NULL;
}

picnixz marked this conversation as resolved.
Show resolved Hide resolved
PyTypeObject *itertype = reversed
? &_PyMemoryRevIter_Type
: &_PyMemoryIter_Type;
memoryiterobject *it = PyObject_GC_New(memoryiterobject, itertype);
if (it == NULL) {
return NULL;
}
it->it_fmt = fmt;
it->it_length = memory_length(self);
it->it_index = reversed ? (it->it_length - 1) : 0;
it->it_seq = _PyMemoryView_CAST(Py_NewRef(self));
_PyObject_GC_TRACK(it);
return (PyObject *)it;
}

static void
memoryiter_dealloc(PyObject *self)
{
memoryiterobject *it = (memoryiterobject *)self;
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
_PyObject_GC_UNTRACK(it);
Py_XDECREF(it->it_seq);
PyObject_GC_Del(it);
Expand All @@ -3315,34 +3358,36 @@ memoryiter_dealloc(PyObject *self)
static int
memoryiter_traverse(PyObject *self, visitproc visit, void *arg)
{
memoryiterobject *it = (memoryiterobject *)self;
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
Py_VISIT(it->it_seq);
return 0;
}

static inline PyObject *
memoryiter_iter_nth(PyMemoryViewObject *seq, Py_ssize_t nth, const char *fmt)
{
CHECK_RELEASED(seq);
const Py_buffer *view = &seq->view;
const char *head = (const char *)seq->view.buf;
const char *ptr = head + view->strides[0] * nth;
ptr = ADJUST_PTR(ptr, view->suboffsets, 0);
if (ptr == NULL) {
return NULL;
}
return unpack_single(seq, ptr, fmt);
}

static PyObject *
memoryiter_next(PyObject *self)
{
memoryiterobject *it = (memoryiterobject *)self;
PyMemoryViewObject *seq;
seq = it->it_seq;
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
PyMemoryViewObject *seq = it->it_seq;
if (seq == NULL) {
return NULL;
}

if (it->it_index < it->it_length) {
CHECK_RELEASED(seq);
Py_buffer *view = &(seq->view);
char *ptr = (char *)seq->view.buf;

ptr += view->strides[0] * it->it_index++;
ptr = ADJUST_PTR(ptr, view->suboffsets, 0);
if (ptr == NULL) {
return NULL;
}
return unpack_single(seq, ptr, it->it_fmt);
return memoryiter_iter_nth(seq, it->it_index++, it->it_fmt);
}

it->it_seq = NULL;
Py_DECREF(seq);
return NULL;
Expand All @@ -3351,38 +3396,7 @@ memoryiter_next(PyObject *self)
static PyObject *
memory_iter(PyObject *seq)
{
if (!PyMemoryView_Check(seq)) {
PyErr_BadInternalCall();
return NULL;
}
PyMemoryViewObject *obj = (PyMemoryViewObject *)seq;
int ndims = obj->view.ndim;
if (ndims == 0) {
PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory");
return NULL;
}
if (ndims != 1) {
PyErr_SetString(PyExc_NotImplementedError,
"multi-dimensional sub-views are not implemented");
return NULL;
}

const char *fmt = adjust_fmt(&obj->view);
if (fmt == NULL) {
return NULL;
}

memoryiterobject *it;
it = PyObject_GC_New(memoryiterobject, &_PyMemoryIter_Type);
if (it == NULL) {
return NULL;
}
it->it_fmt = fmt;
it->it_length = memory_length((PyObject *)obj);
it->it_index = 0;
it->it_seq = (PyMemoryViewObject*)Py_NewRef(obj);
_PyObject_GC_TRACK(it);
return (PyObject *)it;
return memoryiter_new(seq, 0);
}

PyTypeObject _PyMemoryIter_Type = {
Expand All @@ -3398,6 +3412,53 @@ PyTypeObject _PyMemoryIter_Type = {
.tp_iternext = memoryiter_next,
};

/**************************************************************************/
/* Memoryview Reversed Iterator */
/**************************************************************************/

static PyObject *
memorview_reverse_iternext(PyObject *self)
{
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
PyMemoryViewObject *seq = it->it_seq;
if (seq == NULL) {
return NULL;
}
if (it->it_index >= 0 && it->it_index < it->it_length) {
// FEAT(picnixz): can we omit "it->it_index < it->it_length"?
return memoryiter_iter_nth(seq, it->it_index--, it->it_fmt);
}
it->it_seq = NULL;
Py_DECREF(seq);
return NULL;
}

/*[clinic input]
memoryview.__reversed__

Return a reverse iterator over this memory view.
[clinic start generated code]*/

static PyObject *
memoryview___reversed___impl(PyMemoryViewObject *self)
/*[clinic end generated code: output=25f9b4345f4720ad input=3c35e9267ad7fcc6]*/
{
return memoryiter_new((PyObject *)self, 1);
}


PyTypeObject _PyMemoryRevIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"memory_reverseiterator",
sizeof(memoryiterobject),
.tp_dealloc = memoryiter_dealloc,
.tp_getattro = PyObject_GenericGetAttr,
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_traverse = memoryiter_traverse,
.tp_iter = PyObject_SelfIter,
.tp_iternext = memorview_reverse_iternext,
};

PyTypeObject PyMemoryView_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"memoryview", /* tp_name */
Expand Down
2 changes: 2 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,7 @@ extern PyTypeObject _PyAnextAwaitable_Type;
extern PyTypeObject _PyLegacyEventHandler_Type;
extern PyTypeObject _PyLineIterator;
extern PyTypeObject _PyMemoryIter_Type;
extern PyTypeObject _PyMemoryRevIter_Type;
extern PyTypeObject _PyPositionsIterator;
extern PyTypeObject _Py_GenericAliasIterType;

Expand Down Expand Up @@ -2328,6 +2329,7 @@ static PyTypeObject* static_types[] = {
&_PyLineIterator,
&_PyManagedBuffer_Type,
&_PyMemoryIter_Type,
&_PyMemoryRevIter_Type,
&_PyMethodWrapper_Type,
&_PyNamespace_Type,
&_PyNone_Type,
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/globals-to-fix.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Objects/codeobject.c - _PyPositionsIterator -
Objects/genericaliasobject.c - _Py_GenericAliasIterType -
# Not in a .h file:
Objects/memoryobject.c - _PyMemoryIter_Type -
Objects/memoryobject.c - _PyMemoryRevIter_Type -
Objects/unicodeobject.c - _PyUnicodeASCIIIter_Type -
Objects/unionobject.c - _PyUnion_Type -
Python/context.c - _PyContextTokenMissing_Type -
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ Modules/posixmodule.c - _Py_open_cloexec_works -
Modules/posixmodule.c - environ -
Objects/object.c - _Py_GenericAliasIterType -
Objects/object.c - _PyMemoryIter_Type -
Objects/object.c - _PyMemoryRevIter_Type -
Objects/object.c - _PyLineIterator -
Objects/object.c - _PyPositionsIterator -
Python/perf_trampoline.c - _Py_trampoline_func_start -
Expand Down
Loading