mutable list iterators - a proposal

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Jess Austin

    mutable list iterators - a proposal

    hi,

    I like the way that Python does lists, and I love the way it does
    iterators. But I've decided I don't like what it does with iterators
    of lists. Lists are supposed to be mutable sequences, but try to use
    an iterator of a list that you're mutating and watch it all fall to
    pieces. That is, if you change the length of a section of the list
    through which the iterator has already passed, it will lose track of
    where it is. I think this should be fixed for 2.4. I'm not sure if
    this is a bug or a PEP, so I'd like to hear from others on this
    newsgroup.

    I've coded the following Python implementation of how I think lists
    should work. It looks a little odd, and I'm sure that a C
    implementation could be quite a bit more efficient (at least I hope
    that is the case), and it wouldn't leak iterators like this one does.
    But I think I've thought of, and dealt with, all of the weird cases.
    These include: adding before the last yielded item, subtracting before
    the next item to be yielded, adding between the last-yielded and
    next-yielded items, subtracting the next-yielded item, etc. I think
    that this subclass does the right thing in each of these cases. I'm
    curious to see if others agree, and whether they think something like
    this should be in the next version of Python. This code should work
    with 2.3.

    I hope this isn't in conflict with any previous irrevocable
    pronouncements. b-)

    Hope you like it,
    Jess



    import itertools

    class mutable_iterabl e_list(list):
    """just like a list except it iterates gracefully under
    mutation"""
    def __init__(self, object):
    list.__init__(s elf, object)
    # set up a way for other methods to communicate with iterators
    self._mutations = {}
    self._iterator_ keys = itertools.count ()
    def __iter__(self):
    # get a unique name for this iterator and 'register' it
    key = self._iterator_ keys.next()
    self._mutations[key] = []
    i = 0
    try:
    while True:
    # calculate the cumulative effect of all changes to
    the list
    # since the last yield statement
    for mutation in self._mutations[key]:
    delta = 0
    # we must use nesting to accomodate slice deletion
    -
    # if we didn't accumulate in delta, i and j could
    pass
    # each other in the night
    # otherwise a list of (j, d)'s would do
    for j, d in mutation:
    if j < i:
    delta += d
    i += delta
    self._mutations[key] = []
    yield self[i]
    i += 1
    except IndexError:
    pass
    del self._mutations[key] # this iterator needs no further
    updates
    # avoid calling list.__delslice __ and list.__setslice __
    def __delslice__(se lf, i, j):
    self.__delitem_ _(slice(i, j))
    def __setslice__(se lf, i, j, object):
    self.__setitem_ _(slice(i, j), object)
    def __delitem__(sel f, index):
    list.__delitem_ _(self, index)
    if type(index) is slice:
    # range() can't handle None as an argument
    start = index.start or 0
    if index.stop is None:
    stop = len(self)
    else:
    stop = index.stop
    step = index.step or 1
    # record the implicit deletion at each point where it
    occurs
    mutations = zip(range(start , stop, step),
    itertools.repea t(-1))
    # inform each iterator of the changes to the list
    for it in self._mutations :
    self._mutations[it].append(mutatio ns)
    else:
    for it in self._mutations :
    self._mutations[it].append(((index , -1),))
    def __setitem__(sel f, index, object):
    list.__setitem_ _(self, index, object)
    # otherwise __setitem__ has no effect on iterators
    if type(index) is slice and index.step is None:
    start = index.start or 0
    if index.stop is None:
    stop = len(self)
    else:
    stop = index.stop
    mutations = zip(range(start , stop), itertools.repea t(-1))
    for it in self._mutations :
    self._mutations[it].append(mutatio ns)
    # record the insertion at the beginning of the slice -
    this is
    # added separately so that if i was pointing to part
    of the
    # affected slice, it will go back to the beginning of
    the added
    # sequence
    self._mutations[it].append(((start , len(object)),))
    def insert(self, index, object):
    list.insert(sel f, index, object)
    for it in self._mutations :
    self._mutations[it].append(((index , 1),))
    def pop(self, index):
    r = list.pop(self, index)
    for it in self._mutations :
    self._mutations[it].append(((index , -1),))
    return r
    def remove(self, object):
    index = self.index(obje ct)
    list.remove(sel f, object)
    for it in self._mutations :
    self._mutations[it].append(((index , -1),))
  • Aahz

    #2
    Re: mutable list iterators - a proposal

    In article <e4e0340c.04031 50334.4faeb3a3@ posting.google. com>,
    Jess Austin <jaustin@post.h arvard.edu> wrote:[color=blue]
    >
    >I like the way that Python does lists, and I love the way it does
    >iterators. But I've decided I don't like what it does with iterators
    >of lists. Lists are supposed to be mutable sequences, but try to use
    >an iterator of a list that you're mutating and watch it all fall to
    >pieces. That is, if you change the length of a section of the list
    >through which the iterator has already passed, it will lose track of
    >where it is. I think this should be fixed for 2.4. I'm not sure if
    >this is a bug or a PEP, so I'd like to hear from others on this
    >newsgroup.[/color]

    I'll guarantee that it won't be fixed for 2.4. This subject has come up
    many times long before iterators were introduced, and the answer has
    always beent the same: if you want to mutate, make a copy or make *very*
    sure that your mutations don't muck the loop.
    [color=blue]
    >I hope this isn't in conflict with any previous irrevocable
    >pronouncements . b-)[/color]

    Your hope is in vain. ;-)
    --
    Aahz (aahz@pythoncra ft.com) <*> http://www.pythoncraft.com/

    "usenet imitates usenet" --Darkhawk

    Comment

    • Terry Reedy

      #3
      Re: mutable list iterators - a proposal


      "Jess Austin" <jaustin@post.h arvard.edu> wrote in message
      news:e4e0340c.0 403150334.4faeb 3a3@posting.goo gle.com...[color=blue]
      > hi,
      >
      > I like the way that Python does lists, and I love the way it does
      > iterators. But I've decided I don't like what it does with iterators
      > of lists. Lists are supposed to be mutable sequences,[/color]

      and they are.
      [color=blue]
      > but try to use an iterator of a list that you're mutating
      > and watch it all fall to pieces.[/color]

      Replacing items, which is by far the most common need, works fine.
      [color=blue]
      > That is, if you change the length of a section of the list
      > through which the iterator has already passed, it will lose track of
      > where it is.[/color]

      Same is true of dicts. The limitation is documented. The need is
      relatively rare. There are several alternatives, usually better.

      1. make a copy and iterate through that.
      2. create a new object as you iterate (for deletes only, use filter to do
      this)
      3. make a list of desired edits (something like your _mutations list) and
      apply that *after* the iteration to do all the edits at once.

      Note that filter is O(n) timewise. Simply deleting items in place and
      closing up the gap, one at a time, as your approach would appear to do, is
      O(n**2). Applying an edit list in a batch after the initial scan may also
      be O(n) for typical cases.

      4. Actually, for deletion (filtration) in place, there *is* an O(n) single
      scan method. The secret is to *not* change the length of the list while
      iterating but to merely move kept objects to the end of a 'kept' section of
      the list (using two indexes) and then truncate the list (deleting the now
      unneeded tail) *after* the scan.

      5. For insertion while scanning, use an index var in a while loop and bump
      up the index after doing insertions. But notice again that this can make
      for an O(n**2) algorithm and it might really be better to make a list of
      slices and then paste them together in a separate post-scan batch process.

      6. Or, insert a large empty slice (hopefully large enough to accomodate
      the net expansion) at the front of the list before starting and then copy
      and insert into the empty space as you scan. (This is a generalization of
      the deletion algorithm, 4.) If you run out of room due to an initial
      underestimate, make another large insertion. Truncate after if there is
      leftover space at the end.

      Bottom line: because of the O(n**2) trap, insertion and deletion are
      trickier than mere replacement.
      [color=blue]
      > I think this should be fixed for 2.4. I'm not sure if
      > this is a bug or a PEP, so I'd like to hear from others on this
      > newsgroup.[/color]

      The listobject.c code is being reworked to make common list operations
      quite noticeably faster. A slowdown to enable doing something that is
      better done elsewise will not be accepted. In most cases, the limitation
      protects people from making a bad algorithm choice.

      Terry J. Reedy




      Comment

      • Jess Austin

        #4
        Re: mutable list iterators - a proposal

        hi Terry,

        Thanks for your thoughtful response. I've tried to address the points
        you made, but I could have missed something. b-)


        "Terry Reedy" <tjreedy@udel.e du> wrote in message news:<mailman.2 1.1079366957.12 241.python-list@python.org >...[color=blue]
        > Replacing items, which is by far the most common need, works fine.[/color]

        Fair enough.

        [color=blue][color=green]
        > > That is, if you change the length of a section of the list
        > > through which the iterator has already passed, it will lose track of
        > > where it is.[/color]
        >
        > Same is true of dicts. The limitation is documented. The need is[/color]

        Actually, dicts just give up at that point. The fact that lists don't
        implied to me that *someone* thought modification should work with
        iterators. Your "other algorithm" suggestions all seem to
        specifically avoid the idiom that I'm proposing, which is iteration
        over a list that can be modified, such that the loop body can feel the
        logical effects of that modification.

        All of your suggestions are valid ways to deal with the current
        system, by deliberately building more machinery on top of it. But
        none of them allow me to simply code a loop that assumes it will get
        the item that is now after the last one it got, even if it may have
        made changes to the list in the interim. The class I posted does
        allow that.

        [color=blue]
        > Note that filter is O(n) timewise. Simply deleting items in place and
        > closing up the gap, one at a time, as your approach would appear to do, is
        > O(n**2). Applying an edit list in a batch after the initial scan may also
        > be O(n) for typical cases.[/color]
        ..
        ..
        ..[color=blue]
        > Bottom line: because of the O(n**2) trap, insertion and deletion are
        > trickier than mere replacement.[/color]

        I'm not handling the deletion myself; every method of my class besides
        __iter__ calls the corresponding method of list right away. I'll
        grant you that the methods other than __iter__ shouldn't spend so much
        time building sequences to record what their actions were. I think
        that instead they should just save a reference to their index value
        (which is either a number or a slice), and the iterator can deal with
        interpreting the meaning whenever it gets called. I doubt that saving
        a reference to an already existing object would slow any of these
        methods down that much. And of course the methods could make sure
        that there are existing iterators before doing anything else. As for
        the iterator, if coded correctly it only has to go through the work of
        interpreting the mutation records if there are actually mutation
        records. That is, the slowdown that has to be somewhere here would
        only occur if the user specifically coded a loop that modified its
        list.

        I think I'll spend some time implementing some of the above changes to
        see if I can get them to work.

        [color=blue]
        > The listobject.c code is being reworked to make common list operations
        > quite noticeably faster. A slowdown to enable doing something that is
        > better done elsewise will not be accepted. In most cases, the limitation
        > protects people from making a bad algorithm choice.[/color]

        I understand what you're saying here. Most of the community doesn't
        care about this. The only way to get something like this into core is
        to prove it won't cost anyone anything, and then get real lucky. I
        wouldn't put it as "protecting people" so much as I would put it "not
        eating their cycles when they're not looking". I did actually have an
        algorithm in mind when I started down this road. After inputting the
        following code, calling tourney(n) builds and returns a tournament
        with fair pairings for a number of parties seeded 1 through n. For
        example:

        tourney(35)
        (((((1, (32, 33)), (16, 17)), ((8, 25), (9, 24))), (((4, 29), (13,
        20)), ((5, 28), (12, 21)))), ((((2, (31, 34)), (15, 18)), ((7, 26),
        (10, 23))), (((3, (30, 35)), (14, 19)), ((6, 27), (11, 22)))))

        It uses the list code I posted before. It appears to me to be O(n),
        which seems like the best possible for this problem.

        thanks,
        Jess


        import math
        from itertools import izip, chain, repeat
        from mutable_iterabl e_list import mutable_iterabl e_list as mil

        class tree(object):
        def __repr__(self):
        return '('+repr(self.t op)+', '+repr(self.bot tom)+')'
        def SetTop(self, top):
        self.top = top
        def SetBottom(self, bottom):
        self.bottom = bottom

        class seed(object):
        """trees grow from seeds"""
        def __init__(self, setter, value):
        self.setter, self.value = setter, value
        def __call__(self, value):
        t = tree()
        t.SetTop(seed(t .SetTop, self.value))
        t.SetBottom(see d(t.SetBottom, value))
        self.setter(t)
        self.setter = t.SetTop
        return t
        def __repr__(self):
        return repr(self.value )

        def tourney(n):
        """Return a tree structure that reflects a tournament of n
        parties"""
        tournament = tree()
        tournament.SetT op(seed(tournam ent.SetTop, 1))
        seeds = mil((tournament .top,))
        for x, s in izip(range(2, n+1),
        chain(*repeat(s eeds, int(math.log(n, 2))+1))):
        seeds[:0] = [s(x).bottom]
        return tournament.top

        Comment

        • Jess Austin

          #5
          Re: mutable list iterators - a proposal

          aahz@pythoncraf t.com (Aahz) wrote in message news:<c34ebq$9k v$1@panix2.pani x.com>...[color=blue]
          > I'll guarantee that it won't be fixed for 2.4. This subject has come up
          > many times long before iterators were introduced, and the answer has
          > always beent the same: if you want to mutate, make a copy or make *very*
          > sure that your mutations don't muck the loop.[/color]

          If this was hashed out so completely before iterators, I wonder if
          iterators might make a different solution more plausible. Such a
          solution wouldn't have been considered since then, because, well, it
          has already been hashed out.
          [color=blue][color=green]
          > >I hope this isn't in conflict with any previous irrevocable
          > >pronouncements . b-)[/color]
          >
          > Your hope is in vain. ;-)[/color]

          It usually is; still I hope. b-)

          Jess

          Comment

          • Raymond Hettinger

            #6
            Re: mutable list iterators - a proposal

            [Jess Austin][color=blue]
            > hi Terry,
            >
            > Thanks for your thoughtful response. I've tried to address the points
            > you made, but I could have missed something. b-)[/color]

            List iterators likely won't change because
            * it does not encourage an error prone programming style
            * it is documented to perform as it currently does
            * there is likely existing code that relies on the current behavior
            * there are performance issues which changing it
            * there do not seem to be compelling, general use cases to warrant a
            change

            Also, I'm not sure your proposal is self-consistent. If I read it
            correctly, there is a strong notion of having the iterator remember
            the last item emitted even if its position changes. However, with a
            combination of slice deletion and insertion, the notion of the last
            item emitted becomes muddy:

            lyst = range(10)
            it = iter(lyst)
            for i in xrange(5):
            it.next()
            lyst[3:7] = [20]
            print it.next() # Should this print 20 or 7 ?



            [color=blue]
            > All of your suggestions are valid ways to deal with the current
            > system, by deliberately building more machinery on top of it. But
            > none of them allow me to simply code a loop that assumes it will get
            > the item that is now after the last one it got, even if it may have
            > made changes to the list in the interim. The class I posted does
            > allow that.[/color]

            Py2.4 adds a new type, collections.deq ue(), that can reasonably be
            made to do what your asking (workable because there is no slicing,
            just appending or popping from either end and setitem value
            mutations):
            [color=blue][color=green][color=darkred]
            >>> from collections import deque
            >>> d = deque('abcdefg' )
            >>> it = iter(d)
            >>> it.next()[/color][/color][/color]
            'a'[color=blue][color=green][color=darkred]
            >>> it.next()[/color][/color][/color]
            'b'[color=blue][color=green][color=darkred]
            >>> d.extend('hijk' )
            >>> d.appendleft('z ')
            >>> d.appendleft('y ')
            >>> list(it) # this picks up right where it left off[/color][/color][/color]
            ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'][color=blue][color=green][color=darkred]
            >>> d[/color][/color][/color]
            deque(['y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
            'k'])



            Raymond Hettinger

            Comment

            • Jess Austin

              #7
              Re: mutable list iterators - a proposal

              Sorry I didn't respond in a timely fashion; I've been out of town.

              python@rcn.com (Raymond Hettinger) wrote in message news:<5d83790c. 0403170242.759a c8d9@posting.go ogle.com>...[color=blue]
              > List iterators likely won't change because
              > * it does not encourage an error prone programming style
              > * it is documented to perform as it currently does
              > * there is likely existing code that relies on the current behavior
              > * there are performance issues which changing it
              > * there do not seem to be compelling, general use cases to warrant a
              > change[/color]

              These are all reasonable objections.

              [color=blue]
              > Also, I'm not sure your proposal is self-consistent. If I read it
              > correctly, there is a strong notion of having the iterator remember
              > the last item emitted even if its position changes. However, with a
              > combination of slice deletion and insertion, the notion of the last
              > item emitted becomes muddy:
              >
              > lyst = range(10)
              > it = iter(lyst)
              > for i in xrange(5):
              > it.next()
              > lyst[3:7] = [20]
              > print it.next() # Should this print 20 or 7 ?[/color]

              In this case my class returns a 20. My thinking on this was that if a
              slice containing the "normal" next item is replaced by a slice, the
              iterator should go to the beginning of the replacement slice, EXCEPT
              in the case where the new slice is the same length as the old slice,
              in which case the iterator should stay where it is. The exception is
              for backwards compatibility with current uses.

              [color=blue]
              > Py2.4 adds a new type, collections.deq ue(), that can reasonably be
              > made to do what your asking (workable because there is no slicing,
              > just appending or popping from either end and setitem value
              > mutations):
              >[color=green][color=darkred]
              > >>> from collections import deque
              > >>> d = deque('abcdefg' )
              > >>> it = iter(d)
              > >>> it.next()[/color][/color]
              > 'a'[color=green][color=darkred]
              > >>> it.next()[/color][/color]
              > 'b'[color=green][color=darkred]
              > >>> d.extend('hijk' )
              > >>> d.appendleft('z ')
              > >>> d.appendleft('y ')
              > >>> list(it) # this picks up right where it left off[/color][/color]
              > ['c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'][color=green][color=darkred]
              > >>> d[/color][/color]
              > deque(['y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
              > 'k'])[/color]

              This looks great. It would indeed work for the class I had in mind.
              Frankly, the fact that the iterator for deque works *correctly* seems
              to beg the question, "why doesn't the iterator for list work
              correctly?" I know, see above for the reasons. The current list
              iteration stills seems ugly, and I know it's jarring to new users
              because I've been jarred by it, have forgotten it, and then been
              jarred again. As for performance, I think that it could be coded so
              that all performance penalties would be within the iterator, and there
              only after the list has been changed. There follows a revision of the
              code I posted originally, which attempts to demonstrate that.

              cheers,
              Jess


              import itertools

              class muterable_list( list):
              """just like a list except it iterates gracefully under
              mutation"""
              Insertion = object()
              Deletion = object()
              def __init__(self, object_):
              list.__init__(s elf, object_)
              self._mutations = []
              self._iterators = {}
              self._iterators _keys = itertools.count ()
              def __iter__(self):
              key = self._iterators _keys.next()
              self._iterators[key] = []
              i = 0
              try:
              while True:
              for position, direction in self._iterators[key]:
              if isinstance(posi tion, int):
              if position < i:
              if direction is self.Deletion:
              i += -1
              else:
              i += 1
              elif direction is self.Deletion: # slice deletion
              start, stop, step = position.indice s(i)
              i += (start - stop)/step
              elif position.start < i: # slice insertion
              i += position.stop - position.start
              self._iterators[key] = []
              yield self[i]
              i += 1
              for iterator in self._iterators :
              self._iterators[iterator].extend(self._m utations)
              self._mutations = []
              except IndexError:
              del self._iterators[key]
              def __delslice__(se lf, i, j):
              list.__delslice __(self, i, j)
              if self._iterators :
              self._mutations .append((slice( i, j), self.Deletion))
              def __setslice__(se lf, i, j, object_):
              list.__setslice __(self, i, j, object_)
              if self._iterators :
              length = len(object_)
              if length != (j - i):
              self._mutations .append((slice( i, j), self.Deletion))
              self._mutations .append((slice( i, i + length),
              self.Insertion) )
              def __delitem__(sel f, index):
              list.__delitem_ _(self, index)
              if self._iterators :
              self._mutations .append((index, self.Deletion))
              def __setitem__(sel f, index, object_):
              list.__setitem_ _(self, index, object_)
              if self._iterators :
              length = len(object_)
              if (isinstance(ind ex, slice) and (index.step == 1 or
              index.step is None) and length != (index.start -
              index.stop)):
              self._mutations .append((index, self.Deletion))
              self._mutations .append((slice( index.start,
              index.start+len gth),
              self.Insertion) )
              def insert(self, index, object_):
              list.insert(sel f, index, object_)
              if self._iterators :
              self._mutations .append((index, self.Insertion) )
              def pop(self, index):
              r = list.pop(self, index)
              if self._iterators :
              self._mutations .append((index, self.Deletion))
              return r
              def remove(self, object_):
              if self._iterators :
              mutation = self.index(obje ct_), self.Deletion
              list.remove(sel f, object_)
              self._mutations .append(mutatio n)
              else:
              list.remove(sel f, object_)

              Comment

              • Raymond Hettinger

                #8
                Re: mutable list iterators - a proposal

                > > Also, I'm not sure your proposal is self-consistent. If I read it[color=blue][color=green]
                > > correctly, there is a strong notion of having the iterator remember
                > > the last item emitted even if its position changes. However, with a
                > > combination of slice deletion and insertion, the notion of the last
                > > item emitted becomes muddy:
                > >
                > > lyst = range(10)
                > > it = iter(lyst)
                > > for i in xrange(5):
                > > it.next()
                > > lyst[3:7] = [20]
                > > print it.next() # Should this print 20 or 7 ?[/color]
                >
                > In this case my class returns a 20. My thinking on this was that if a
                > slice containing the "normal" next item is replaced by a slice,[/color]

                When designing a new type, the trick is to avoid areas where two
                different people could reasonably expect different "normal" behaviors.
                Otherwise, you're doing more harm than good and making it likely that
                your users will stumble into a class of bugs that are extremely
                difficult to hunt down.

                They are truly much better off with the paradigm of looping through
                one list and building another. In-place alteration is as icebergs are
                to the Titanic. Only the simplest cases are reasonable (changing
                values but not size; or reverse iterating and popping away items after
                the current position).
                [color=blue][color=green]
                > > Py2.4 adds a new type, collections.deq ue(), that can reasonably[/color][/color]
                be[color=blue][color=green]
                > > made to do what your asking (workable because there is no slicing,
                > > just appending or popping from either end and setitem value
                > > mutations):[/color][/color]
                . . .[color=blue]
                > This looks great. It would indeed work for the class I had in mind.
                > Frankly, the fact that the iterator for deque works *correctly* seems
                > to beg the question, "why doesn't the iterator for list work
                > correctly?"[/color]

                What makes lists different is that slicing operations can alter them
                in the middle. The inconsistencies disappear at the end points.
                Deques only mutate at the ends, so you always have a clear definition
                of "what's next".


                [color=blue]
                > There follows a revision of the
                > code I posted originally, which attempts to demonstrate that.[/color]

                The code is an improvement, but still suffers from the definitional
                ambiguity mentioned above. I believe this is inescapable. Consider a
                line at movie, you leave the line, others join in the middle, others
                leave, a few more join, a few more leave, now who is the person who
                "naturally" comes after you.

                All the contortions in the code still suggest to me that the use case
                needs a different algorithm. Try rewriting it with separate input and
                output lists. My bet is that the code is clearer and more
                maintainable.


                [color=blue]
                > import itertools[/color]

                Hey, hey, I love to see people using my module ;-)

                [color=blue]
                > class muterable_list( list):
                > """just like a list except it iterates gracefully under
                > mutation"""[/color]

                There is one word in the docstring that is debatable. Any guesses ;-)



                Raymond Hettinger

                Comment

                • Jess Austin

                  #9
                  Re: mutable list iterators - a proposal

                  I hope I'm not belaboring the point. b-)

                  python@rcn.com (Raymond Hettinger) wrote in message news:<5d83790c. 0403211210.7039 91c2@posting.go ogle.com>...[color=blue]
                  > When designing a new type, the trick is to avoid areas where two
                  > different people could reasonably expect different "normal" behaviors.
                  > Otherwise, you're doing more harm than good and making it likely that
                  > your users will stumble into a class of bugs that are extremely
                  > difficult to hunt down.[/color]

                  This seems like good advice, but the current implementation of lists
                  in Python doesn't follow it wrt modifying while iterating. People who
                  remember all the footnotes to the docs expect one thing, and the rest
                  of us expect something else. Maybe it would be better if list
                  iterators puked in the same fashion that dict iterators do immediately
                  after the dict instance is modified. Then users wouldn't start by
                  appending to lists over which they are iterating (which works,
                  although I'm not sure it will with the new reversed iterators), and go
                  on to expect to be able to modify lists in a general fashion.

                  [color=blue]
                  > The code is an improvement, but still suffers from the definitional
                  > ambiguity mentioned above. I believe this is inescapable. Consider a[/color]

                  Thanks. I feel that in allowing an operation that was previously
                  implicitly forbidden, one can be forgiven if there is some subtlety to
                  the semantics of the operation. But that's debatable.

                  [color=blue]
                  > All the contortions in the code still suggest to me that the use case
                  > needs a different algorithm. Try rewriting it with separate input and
                  > output lists. My bet is that the code is clearer and more
                  > maintainable.[/color]

                  My original use case was 3 classes in 30 lines, not including the
                  subclassing of list. I thought the subclassing of list was fairly
                  componentized, and it would be unnecessary if the list implementation
                  were modified as I've suggested. That doesn't look too likely. b-)
                  I'll certainly be using collections.deq ue after I install 2.4!

                  [color=blue][color=green]
                  > > import itertools[/color]
                  >
                  > Hey, hey, I love to see people using my module ;-)[/color]

                  I love to use it. Iterators and list comprehensions were good, but
                  coupled with itertools they are great.

                  Thanks, Raymond, for all you've done for Python.

                  later,
                  Jess

                  Comment

                  • Raymond Hettinger

                    #10
                    Re: mutable list iterators - a proposal

                    [Raymond][color=blue][color=green]
                    > > All the contortions in the code still suggest to me that the use case
                    > > needs a different algorithm. Try rewriting it with separate input and
                    > > output lists. My bet is that the code is clearer and more
                    > > maintainable.[/color][/color]

                    [Jess Austin][color=blue]
                    > My original use case was 3 classes in 30 lines, not including the
                    > subclassing of list.[/color]

                    Depending on your application, there may be a good alternative to
                    mutating in-place while iterating. Consider using a Queue object or
                    something similar (collections.de que in Py2.4, deque objects on ASPN,
                    or lists with append() and pop(0)):

                    while inputlist:
                    datum = inputlist.pop(0 )
                    [processing with the element]
                    if somecondition:
                    inputlist.appen d(datum)
                    else:
                    continue # effectively removing the element

                    The idea is to get around the issues of mutating in-place by removing
                    the element in question, processing it, and if needed again, adding to
                    the end of the queue.

                    The ASPN recipes on queues show show to do this a bit more efficiently
                    than with the list object but the idea is the same.


                    [color=blue]
                    > I love to use it. Iterators and list comprehensions were good, but
                    > coupled with itertools they are great.
                    >
                    > Thanks, Raymond, for all you've done for Python.[/color]

                    Thanks for the accolades.


                    Raymond

                    Comment

                    Working...