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