Skip to content

Commit dd84c88

Browse files
committed
Add reversed() iterator.
1 parent 5e1307e commit dd84c88

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

doc/sphinx/source/iterators_generators.rst

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,80 @@ Result in the stdout:
656656
assert captured.out == expected
657657
658658
659+
.. _PySequenceMethods: https://docs.python.org/3/c-api/typeobj.html#c.PySequenceMethods
660+
661+
-----------------------------------------
662+
Reverse Iterators
663+
-----------------------------------------
664+
665+
Reverse iterators are slightly unusual, the ``reversed()`` function calls the object ``__reversed__`` method and that
666+
in turn uses ``__len__`` and ``__getitem__``.
667+
668+
To support this in C we need to implement these last two methods using the `PySequenceMethods`_ method table.
669+
670+
First the implementation of ``__len__`` in C:
671+
672+
.. code-block:: c
673+
674+
static Py_ssize_t
675+
SequenceOfLong_len(PyObject *self) {
676+
return ((SequenceOfLong *)self)->size;
677+
}
678+
679+
Then the implementation of ``__getitem__``, not here that we support negative indexes and set and exception if the
680+
index is out of range:
681+
682+
.. code-block:: c
683+
684+
static PyObject *
685+
SequenceOfLong_getitem(PyObject *self, Py_ssize_t index) {
686+
Py_ssize_t my_index = index;
687+
if (my_index < 0) {
688+
my_index += SequenceOfLong_len(self);
689+
}
690+
if (my_index > SequenceOfLong_len(self)) {
691+
PyErr_Format(
692+
PyExc_IndexError,
693+
"Index %ld is out of range for length %ld",
694+
index,
695+
SequenceOfLong_len(self)
696+
);
697+
return NULL;
698+
}
699+
return PyLong_FromLong(((SequenceOfLong *)self)->array_long[my_index]);
700+
}
701+
702+
Create `PySequenceMethods`_ method table:
703+
704+
.. code-block:: c
705+
706+
PySequenceMethods SequenceOfLong_sequence_methods = {
707+
.sq_length = &SequenceOfLong_len,
708+
.sq_item = &SequenceOfLong_getitem,
709+
};
710+
711+
Add this method table into the type specification:
712+
713+
.. code-block:: c
714+
715+
static PyTypeObject SequenceOfLongType = {
716+
PyVarObject_HEAD_INIT(NULL, 0)
717+
/* Other stuff. */
718+
.tp_dealloc = (destructor) SequenceOfLong_dealloc,
719+
.tp_as_sequence = &SequenceOfLong_sequence_methods,
720+
/* Other stuff. */
721+
};
722+
723+
And we can test it thus:
724+
725+
.. code-block:: python
726+
727+
def test_c_iterator_reversed():
728+
sequence = cIterator.SequenceOfLong([1, 7, 4])
729+
result = reversed(sequence)
730+
assert list(result) == [4, 7, 1,]
731+
732+
659733
.. index::
660734
single: Generators
661735

src/cpy/Iterators/cIterator.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,35 @@ static PyMethodDef SequenceOfLong_methods[] = {
193193
{NULL, NULL, 0, NULL} /* Sentinel */
194194
};
195195

196+
/* Sequence methods. */
197+
static Py_ssize_t
198+
SequenceOfLong_len(PyObject *self) {
199+
return ((SequenceOfLong *)self)->size;
200+
}
201+
202+
static PyObject *
203+
SequenceOfLong_getitem(PyObject *self, Py_ssize_t index) {
204+
Py_ssize_t my_index = index;
205+
if (my_index < 0) {
206+
my_index += SequenceOfLong_len(self);
207+
}
208+
if (my_index > SequenceOfLong_len(self)) {
209+
PyErr_Format(
210+
PyExc_IndexError,
211+
"Index %ld is out of range for length %ld",
212+
index,
213+
SequenceOfLong_len(self)
214+
);
215+
return NULL;
216+
}
217+
return PyLong_FromLong(((SequenceOfLong *)self)->array_long[my_index]);
218+
}
219+
220+
PySequenceMethods SequenceOfLong_sequence_methods = {
221+
.sq_length = &SequenceOfLong_len,
222+
.sq_item = &SequenceOfLong_getitem,
223+
};
224+
196225
static PyObject *
197226
SequenceOfLong___str__(SequenceOfLong *self, PyObject *Py_UNUSED(ignored)) {
198227
assert(!PyErr_Occurred());
@@ -205,6 +234,7 @@ static PyTypeObject SequenceOfLongType = {
205234
.tp_basicsize = sizeof(SequenceOfLong),
206235
.tp_itemsize = 0,
207236
.tp_dealloc = (destructor) SequenceOfLong_dealloc,
237+
.tp_as_sequence = &SequenceOfLong_sequence_methods,
208238
.tp_str = (reprfunc) SequenceOfLong___str__,
209239
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
210240
.tp_doc = "Sequence of long integers.",

tests/unit/test_c_iterators.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ def test_c_iterator_sequence_of_long_dir_pre_311():
2929
'__format__',
3030
'__ge__',
3131
'__getattribute__',
32+
'__getitem__',
3233
'__gt__',
3334
'__hash__',
3435
'__init__',
3536
'__init_subclass__',
3637
'__iter__',
3738
'__le__',
39+
'__len__',
3840
'__lt__',
3941
'__ne__',
4042
'__new__',
@@ -59,13 +61,15 @@ def test_c_iterator_sequence_of_long_dir_311_plus():
5961
'__format__',
6062
'__ge__',
6163
'__getattribute__',
64+
'__getitem__',
6265
'__getstate__',
6366
'__gt__',
6467
'__hash__',
6568
'__init__',
6669
'__init_subclass__',
6770
'__iter__',
6871
'__le__',
72+
'__len__',
6973
'__lt__',
7074
'__ne__',
7175
'__new__',
@@ -210,6 +214,14 @@ def test_c_iterator_sorted():
210214
assert original == [1, 7, 4, ]
211215

212216

217+
def test_c_iterator_reversed():
218+
sequence = cIterator.SequenceOfLong([1, 7, 4])
219+
result = reversed(sequence)
220+
print()
221+
print(result)
222+
assert list(result) == [4, 7, 1,]
223+
224+
213225
def test_c_iterator_generator_expression_sum():
214226
"""https://docs.python.org/3/glossary.html#term-generator-expression"""
215227
sequence = cIterator.SequenceOfLong([1, 7, 4])

0 commit comments

Comments
 (0)