From 85c20a41dfcec04d161ad7da7260e7b94c62d228 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 6 Nov 2003 14:06:48 +0000 Subject: [PATCH] Implement and apply PEP 322, reverse iteration --- Doc/lib/libfuncs.tex | 8 +++ Include/enumobject.h | 1 + Lib/heapq.py | 2 +- Lib/mhlib.py | 3 +- Lib/platform.py | 2 +- Lib/random.py | 2 +- Lib/rfc822.py | 3 +- Lib/test/test_enumerate.py | 20 +++++- Misc/NEWS | 3 + Objects/enumobject.c | 125 +++++++++++++++++++++++++++++++++++++ Objects/rangeobject.c | 37 ++++++++++- Python/bltinmodule.c | 1 + 12 files changed, 198 insertions(+), 9 deletions(-) diff --git a/Doc/lib/libfuncs.tex b/Doc/lib/libfuncs.tex index a9f3a65a702..7d64f936380 100644 --- a/Doc/lib/libfuncs.tex +++ b/Doc/lib/libfuncs.tex @@ -880,6 +880,14 @@ class C(object): when passed to \function{eval()}. \end{funcdesc} +\begin{funcdesc}{reversed}{seq} + Return a reverse iterator. \var{seq} must be an object which + supports the sequence protocol (the __len__() method and the + \method{__getitem__()} method with integer arguments starting at + \code{0}). + \versionadded{2.4} +\end{funcdesc} + \begin{funcdesc}{round}{x\optional{, n}} Return the floating point value \var{x} rounded to \var{n} digits after the decimal point. If \var{n} is omitted, it defaults to zero. diff --git a/Include/enumobject.h b/Include/enumobject.h index 053fb72a5ec..c14dbfc8c37 100644 --- a/Include/enumobject.h +++ b/Include/enumobject.h @@ -8,6 +8,7 @@ extern "C" { #endif PyAPI_DATA(PyTypeObject) PyEnum_Type; +PyAPI_DATA(PyTypeObject) PyReversed_Type; #ifdef __cplusplus } diff --git a/Lib/heapq.py b/Lib/heapq.py index 2c30b12f220..2223a97fa4c 100644 --- a/Lib/heapq.py +++ b/Lib/heapq.py @@ -165,7 +165,7 @@ def heapify(x): # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1. - for i in xrange(n//2 - 1, -1, -1): + for i in reversed(xrange(n//2)): _siftup(x, i) # 'heap' is a heap at all indices >= startpos, except possibly for pos. pos diff --git a/Lib/mhlib.py b/Lib/mhlib.py index 5520f8238e6..899939a4937 100644 --- a/Lib/mhlib.py +++ b/Lib/mhlib.py @@ -975,8 +975,7 @@ def test(): print seqs f.putsequences(seqs) do('f.getsequences()') - testfolders.reverse() - for t in testfolders: do('mh.deletefolder(%s)' % `t`) + for t in reversed(testfolders): do('mh.deletefolder(%s)' % `t`) do('mh.getcontext()') context = mh.getcontext() f = mh.openfolder(context) diff --git a/Lib/platform.py b/Lib/platform.py index 31c47b4c9ad..389e50cb2c7 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -201,7 +201,7 @@ def _dist_try_harder(distname,version,id): if os.path.isdir('/usr/lib/setup'): # Check for slackware verson tag file (thanks to Greg Andruk) verfiles = os.listdir('/usr/lib/setup') - for n in range(len(verfiles)-1, -1, -1): + for n in reversed(xrange(len(verfiles))): if verfiles[n][:14] != 'slack-version-': del verfiles[n] if verfiles: diff --git a/Lib/random.py b/Lib/random.py index 16b667a3af2..1f279bb8782 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -253,7 +253,7 @@ class Random(_random.Random): if random is None: random = self.random - for i in xrange(len(x)-1, 0, -1): + for i in reversed(xrange(1, len(x))): # pick an element in x[:i+1] with which to exchange x[i] j = int(random() * (i+1)) x[i], x[j] = x[j], x[i] diff --git a/Lib/rfc822.py b/Lib/rfc822.py index 4f69b22aabd..9a52a90d1db 100644 --- a/Lib/rfc822.py +++ b/Lib/rfc822.py @@ -421,8 +421,7 @@ class Message: hit = 0 if hit: list.append(i) - list.reverse() - for i in list: + for i in reversed(list): del self.headers[i] def setdefault(self, name, default=""): diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 88e24e8aba9..b07360600a0 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -124,9 +124,27 @@ class TestBig(EnumerateTestCase): seq = range(10,20000,2) res = zip(range(20000), seq) +class TestReversed(unittest.TestCase): + + def test_simple(self): + class A: + def __getitem__(self, i): + if i < 5: + return str(i) + raise StopIteration + def __len__(self): + return 5 + for data in 'abc', range(5), tuple(enumerate('abc')), A(), xrange(1,17,5): + self.assertEqual(list(data)[::-1], list(reversed(data))) + self.assertRaises(TypeError, reversed, {}) + + def test_xrange_optimization(self): + x = xrange(1) + self.assertEqual(type(reversed(x)), type(iter(x))) def test_main(verbose=None): - testclasses = (EnumerateTestCase, SubclassTestCase, TestEmpty, TestBig) + testclasses = (EnumerateTestCase, SubclassTestCase, TestEmpty, TestBig, + TestReversed) test_support.run_unittest(*testclasses) # verify reference counting diff --git a/Misc/NEWS b/Misc/NEWS index c47ed158afc..838cfc8009b 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.4 alpha 1? Core and builtins ----------------- +- Added a reversed() builtin function that returns a reverse iterator + over a sequence. + - CObjects are now mutable (on the C level) through PyCObject_SetVoidPtr. - list.sort() now supports three keyword arguments: cmp, key, and reverse. diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 17a62825a09..998e3810a1d 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -155,3 +155,128 @@ PyTypeObject PyEnum_Type = { enum_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ }; + +/* Reversed Object ***************************************************************/ + +typedef struct { + PyObject_HEAD + long index; + PyObject* seq; +} reversedobject; + +static PyObject * +reversed_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + long n; + PyObject *seq; + reversedobject *ro; + + if (!PyArg_UnpackTuple(args, "reversed", 1, 1, &seq)) + return NULL; + + /* Special case optimization for xrange */ + if (PyRange_Check(seq)) + return PyObject_CallMethod(seq, "__reversed__", NULL); + + if (!PySequence_Check(seq)) { + PyErr_SetString(PyExc_TypeError, + "argument to reversed() must be a sequence"); + return NULL; + } + + n = PySequence_Size(seq); + if (n == -1) + return NULL; + + ro = (reversedobject *)type->tp_alloc(type, 0); + if (ro == NULL) + return NULL; + + ro->index = n-1; + Py_INCREF(seq); + ro->seq = seq; + return (PyObject *)ro; +} + +static void +reversed_dealloc(reversedobject *ro) +{ + PyObject_GC_UnTrack(ro); + Py_XDECREF(ro->seq); + ro->ob_type->tp_free(ro); +} + +static int +reversed_traverse(reversedobject *ro, visitproc visit, void *arg) +{ + if (ro->seq) + return visit((PyObject *)(ro->seq), arg); + return 0; +} + +static PyObject * +reversed_next(reversedobject *ro) +{ + PyObject *item; + + if (ro->index < 0) + return NULL; + + assert(PySequence_Check(ro->seq)); + item = PySequence_GetItem(ro->seq, ro->index); + if (item == NULL) + return NULL; + + ro->index--; + return item; +} + +PyDoc_STRVAR(reversed_doc, +"reverse(sequence) -> reverse iterator over values of the sequence\n" +"\n" +"Return a reverse iterator"); + +PyTypeObject PyReversed_Type = { + PyObject_HEAD_INIT(&PyType_Type) + 0, /* ob_size */ + "reversed", /* tp_name */ + sizeof(reversedobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)reversed_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + reversed_doc, /* tp_doc */ + (traverseproc)reversed_traverse,/* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)reversed_next, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + reversed_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 299f4a651f9..1f5672804f3 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -171,6 +171,15 @@ static PySequenceMethods range_as_sequence = { }; static PyObject * range_iter(PyObject *seq); +static PyObject * range_reverse(PyObject *seq); + +PyDoc_STRVAR(reverse_doc, +"Returns a reverse iterator."); + +static PyMethodDef range_methods[] = { + {"__reversed__", (PyCFunction)range_reverse, METH_NOARGS, reverse_doc}, + {NULL, NULL} /* sentinel */ +}; PyTypeObject PyRange_Type = { PyObject_HEAD_INIT(&PyType_Type) @@ -201,7 +210,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_weaklistoffset */ (getiterfunc)range_iter, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + range_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ @@ -245,6 +254,32 @@ range_iter(PyObject *seq) return (PyObject *)it; } +static PyObject * +range_reverse(PyObject *seq) +{ + rangeiterobject *it; + long start, step, len; + + if (!PyRange_Check(seq)) { + PyErr_BadInternalCall(); + return NULL; + } + it = PyObject_New(rangeiterobject, &Pyrangeiter_Type); + if (it == NULL) + return NULL; + + start = ((rangeobject *)seq)->start; + step = ((rangeobject *)seq)->step; + len = ((rangeobject *)seq)->len; + + it->index = 0; + it->start = start + (len-1) * step; + it->step = -step; + it->len = len; + + return (PyObject *)it; +} + static PyObject * rangeiter_next(rangeiterobject *r) { diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 0309f1de559..35283fc00ca 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2121,6 +2121,7 @@ _PyBuiltin_Init(void) SETBUILTIN("list", &PyList_Type); SETBUILTIN("long", &PyLong_Type); SETBUILTIN("object", &PyBaseObject_Type); + SETBUILTIN("reversed", &PyReversed_Type); SETBUILTIN("slice", &PySlice_Type); SETBUILTIN("staticmethod", &PyStaticMethod_Type); SETBUILTIN("str", &PyString_Type);