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 10 commits into
base: main
Choose a base branch
from
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.

164 changes: 113 additions & 51 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,49 @@ typedef struct {
const char *it_fmt;
} memoryiterobject;

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

ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
static PyObject *
memoryiter_new(PyObject *self, int reversed)
{
assert(reversed == 0 || reversed == 1);
assert(PyMemoryView_Check(self));

PyMemoryViewObject *sequence = _PyMemoryView_CAST(self);
picnixz marked this conversation as resolved.
Show resolved Hide resolved
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 = (PyMemoryViewObject *)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);
picnixz marked this conversation as resolved.
Show resolved Hide resolved
_PyObject_GC_UNTRACK(it);
Py_XDECREF(it->it_seq);
PyObject_GC_Del(it);
Expand All @@ -3315,89 +3356,110 @@ memoryiter_dealloc(PyObject *self)
static int
memoryiter_traverse(PyObject *self, visitproc visit, void *arg)
{
memoryiterobject *it = (memoryiterobject *)self;
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
picnixz marked this conversation as resolved.
Show resolved Hide resolved
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) {
picnixz marked this conversation as resolved.
Show resolved Hide resolved
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);
picnixz marked this conversation as resolved.
Show resolved Hide resolved
PyMemoryViewObject *seq = it->it_seq;
if (seq == NULL) {
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
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);
Py_CLEAR(it->it_seq);
return NULL;
}

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 memoryiter_new(seq, 0);
}

PyTypeObject _PyMemoryIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
.tp_name = "memory_iterator",
.tp_basicsize = sizeof(memoryiterobject),
.tp_dealloc = memoryiter_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_traverse = memoryiter_traverse,
.tp_iter = PyObject_SelfIter,
.tp_iternext = memoryiter_next,
};

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

static PyObject *
memorview_reverse_iternext(PyObject *self)
{
memoryiterobject *it = _PyMemoryViewIter_CAST(self);
picnixz marked this conversation as resolved.
Show resolved Hide resolved
PyMemoryViewObject *seq = it->it_seq;
if (seq == NULL) {
return NULL;
}
if (ndims != 1) {
PyErr_SetString(PyExc_NotImplementedError,
"multi-dimensional sub-views are not implemented");
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);
}
Py_CLEAR(it->it_seq);
return NULL;
}

const char *fmt = adjust_fmt(&obj->view);
if (fmt == NULL) {
return NULL;
}
/*[clinic input]
memoryview.__reversed__

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 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]*/
{
// NOTE(picnixz): generic implementation via reversed(...) is faster
// if the iterator is not used or if zero or few items are iterated.
//
// When the number of items is sufficiently large (>= 2^5), benchmarks
// show that this implementation improves for-loop performances. Note
// that materializing the specialized reversed iterator is likely to
// be slower than materializing a generic reversed object instance.
return memoryiter_new((PyObject *)self, 1);
}

PyTypeObject _PyMemoryIter_Type = {

PyTypeObject _PyMemoryRevIter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
.tp_name = "memory_iterator",
.tp_name = "memory_reverseiterator",
.tp_basicsize = sizeof(memoryiterobject),
// methods
.tp_dealloc = memoryiter_dealloc,
.tp_getattro = PyObject_GenericGetAttr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_traverse = memoryiter_traverse,
.tp_iter = PyObject_SelfIter,
.tp_iternext = memoryiter_next,
.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