Comment on PEP-0322: Reverse Iteration Methods
Stephen Horne
$$$$$$$$$$$$$$$$$ at $$$$$$$$$$$$$$$$$$$$.co.uk
Wed Sep 24 23:41:28 EDT 2003
On Thu, 25 Sep 2003 00:58:45 GMT, "Raymond Hettinger"
<vze4rx4y at verizon.net> wrote:
>* Use object properties instead of methods.
>
> I can't yet claim to understand what the author is really
> proposing. It has something to do which providing
> access to an object that responds to iter, getitem, and
> getslice with reversed indices.
The idea is basically to have a way to get a proxy to a sequence which
behaves like the original sequence, but with indexes reversed. There
is no reason why member functions couldn't return those proxies
instead of using properties, except that when the result is sliced it
looks less ugly.
I have a demo of it for lists only (listing at end of post). It has
three properties...
backward : reversed, slicing returns a list
ibackward : reversed, slicing returns an iterator
iforward : forward, slicing returns an iterator
All properties currently support __getitem__, __setitem__,
__delitem__, __str__, __repr__, __iter__ and __len__ (they all return
an instance of the same proxy class but with different options set).
For simple iterating-over-ints, I'd use the same principles with that
object-acting-as-sliceable-set-of-all-integers idea from the PEP284
thread.
I had some problems getting the slice translation to work - there are
some little nasties with negative steps. You can write...
x [::-1]
...but trying to make it explicit...
x [len(x)-1:-1:-1]
...breaks it because of that -ve stop bound. Not being a Numeric user,
slice steps are quite a new thing to me - and that little trap just
caught me by surprise. It isn't too hard to work around it, though.
The code following isn't pretty but so far as I can tell it works.
Python 2.3 is assumed.
The question is - is the exercise worthwhile. I'm not sure even though
it's my own idea. I think it is nice in its way, and a lot more
flexible than the 'iter_backward' method proposal, but that is quite
likely a bad thing as it is probably massive overkill for the problem
being solved - and in particular the extra flexibility has costs.
What the hell, though - it was fun to play with for a bit!
Anyway, here is a quick test session...
>>> from rilist import rilist
>>> x=rilist(range(20))
>>> x.backward
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> x.backward[:10]
[19, 18, 17, 16, 15, 14, 13, 12, 11, 10]
>>> x.backward[::2]
[19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
>>> del x.backward[::2]
>>> x.backward
[18, 16, 14, 12, 10, 8, 6, 4, 2, 0]
>>> x
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> for i in x.backward :
... print i
...
18
16
14
12
10
8
6
4
2
0
>>> for i in x.backward [5:]:
... print i
...
8
6
4
2
0
>>> for i in x [5:]:
... print i
...
10
12
14
16
18
I've done some 'ibackward' tests as well, and that seems to work - but
I haven't bothered with 'iforward'.
Here is the source - warning, it isn't very self-documenting ATM...
class c_List_Proxy (object) :
"""
Proxy to list class which supports a subset of methods with
modified behaviours, mainly...
- Slicing may create an iterator rather than a list.
- Subscripts and ranges may be reversed.
"""
def __init__ (self, p_List, p_Reverse=False, p_Iter_Slice=False) :
self.List = p_List
self.Reverse = p_Reverse
self.Iter_Slice = p_Iter_Slice
# need some support stuff
def __SliceIter__ (self, p_Slice) :
if p_Slice.step > 0 :
i = p_Slice.start
while i < p_Slice.stop :
yield self.List [i]
i += p_Slice.step
else :
i = p_Slice.start
while i > p_Slice.stop :
yield self.List [i]
i += p_Slice.step
def __FullIter__ (self) :
i = len (self.List) - 1
while i >= 0 :
yield self.List [i]
i -= 1
def __KeyTransformed__ (self, p_Key) :
if self.Reverse :
if p_Key < 0 :
return (-1) - p_Key
else :
return (len (self.List) - 1) - p_Key
else :
return p_Key
def __Bound__ (self, p_Given, p_Default) :
if p_Given is None :
return p_Default
elif p_Given < 0 :
return len (self.List) + p_Given
else :
return p_Given
def __SliceFixed__ (self, p_Slice) :
l_Step = p_Slice.step or 1
if l_Step == 0 :
raise IndexError, "Step must be non-zero"
elif l_Step > 0 :
l_Start = self.__Bound__ (p_Slice.start, 0)
l_Stop = self.__Bound__ (p_Slice.stop, len (self.List))
else :
l_Start = self.__Bound__ (p_Slice.start, len (self.List) - 1)
l_Stop = self.__Bound__ (p_Slice.stop, -1)
l_Count = (((l_Stop - 1) - l_Start) // l_Step) + 1
l_Stop = l_Start + l_Count * l_Step
return slice(l_Start,l_Stop,l_Step)
def __SliceTransformed__ (self, p_Slice) :
l_Slice = self.__SliceFixed__ (p_Slice)
if self.Reverse :
l_End = len(self.List) - 1
return slice (l_End - l_Slice.start, l_End - l_Slice.stop,
-l_Slice.step)
else :
return p_Slice
# some members are trivial
def __len__ (self) :
return len (self.List)
def __iter__ (self) :
return self.__FullIter__ ()
# some members need a bit more work...
def __str__ (self) :
if self.Reverse :
return str(self.List [::-1])
else :
return str(self.List)
def __repr__ (self) :
if self.Reverse :
return repr(self.List [::-1])
else :
return repr(self.List)
def __getitem__ (self, p_Item) :
if isinstance (p_Item, slice) :
if self.Iter_Slice :
return self.__SliceIter__ (self.__SliceTransformed__ (p_Item))
else :
l_Slice = self.__SliceTransformed__ (p_Item)
if l_Slice.stop == -1 : # annoying special case
return self.List [l_Slice.start::l_Slice.step]
return self.List [l_Slice.start:l_Slice.stop:l_Slice.step]
else :
return self.List [self.__KeyTransformed__ (p_Item)]
def __setitem__ (self, p_Item, p_Value) :
if isinstance (p_Item, slice) :
l_Slice = self.__SliceTransformed__ (p_Item)
if l_Slice.stop == -1 : # annoying special case
self.List [l_Slice.start::l_Slice.step] = p_Value
else :
self.List [l_Slice.start:l_Slice.stop:l_Slice.step] = p_Value
else :
self.List [self.__KeyTransformed__ (p_Item)] = p_Value
def __delitem__ (self, p_Item) :
if isinstance (p_Item, slice) :
l_Slice = self.__SliceTransformed__ (p_Item)
if l_Slice.stop == -1 : # annoying special case
del self.List [l_Slice.start::l_Slice.step]
else :
del self.List [l_Slice.start:l_Slice.stop:l_Slice.step]
else :
del self.List [self.__KeyTransformed__ (p_Item)]
class rilist (list) :
"""
Extend normal list to provide properties giving variants of normal
indexing behaviour - specifically...
indexing slicing returns
-------- ---------------
iforward forward iterator
ibackward backward iterator
backward backward list
"""
def __IFwd__ (self) :
return c_List_Proxy (self, False, True)
def __IBack__ (self) :
return c_List_Proxy (self, True, True)
def __Back__ (self) :
return c_List_Proxy (self, True, False)
iforward = property(__IFwd__ )
ibackward = property(__IBack__)
backward = property(__Back__ )
--
Steve Horne
steve at ninereeds dot fsnet dot co dot uk
More information about the Python-list
mailing list