text: from pypy.interpreter.baseobjspace import Wrappable
  from pypy.interpreter.error import OperationError
  from pypy.interpreter.typedef import TypeDef, make_weakref_descr
  from pypy.interpreter.gateway import interp2app, ObjSpace, W_Root
  from pypy.rlib.rarithmetic import ovfcheck
  
  class W_Count(Wrappable):
  
      def __init__(self, space, firstval):
          self.space = space
          self.c = firstval
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          c = self.c
          try:
              self.c = ovfcheck(self.c + 1)
          except OverflowError:
              raise OperationError(self.space.w_OverflowError,
                      self.space.wrap("cannot count beyond sys.maxint"))
  
          return self.space.wrap(c)
  
      def repr_w(self):
          s = 'count(%d)' % (self.c,)
          return self.space.wrap(s)
  
  
  def W_Count___new__(space, w_subtype, firstval=0):
      return space.wrap(W_Count(space, firstval))
  
  W_Count.typedef = TypeDef(
          'count',
          __new__ = interp2app(W_Count___new__, unwrap_spec=[ObjSpace, W_Root, int]),
          __iter__ = interp2app(W_Count.iter_w, unwrap_spec=['self']),
          next = interp2app(W_Count.next_w, unwrap_spec=['self']),
          __repr__ = interp2app(W_Count.repr_w, unwrap_spec=['self']),
          __doc__ = """Make an iterator that returns consecutive integers starting
      with n.  If not specified n defaults to zero. Does not currently
      support python long integers. Often used as an argument to imap()
      to generate consecutive data points.  Also, used with izip() to
      add sequence numbers.
  
      Equivalent to :
  
      def count(n=0):
          if not isinstance(n, int):
              raise TypeError("%s is not a regular integer" % n)
          while True:
              yield n
              n += 1
      """)
  
  
  class W_Repeat(Wrappable):
  
      def __init__(self, space, w_obj, w_times):
          self.space = space
          self.w_obj = w_obj
          
          if space.is_w(w_times, space.w_None):
              self.counting = False
              self.count = 0
          else:
              self.counting = True
              self.count = self.space.int_w(w_times)
  
      def next_w(self):
          if self.counting:
              if self.count <= 0:
                  raise OperationError(self.space.w_StopIteration, self.space.w_None)
              self.count -= 1
          return self.w_obj
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def repr_w(self):
          objrepr = self.space.str_w(self.space.repr(self.w_obj))
          if self.counting:
              s = 'repeat(%s, %d)' % (objrepr, self.count)
          else:
              s = 'repeat(%s)' % (objrepr,)
          return self.space.wrap(s)
  
  def W_Repeat___new__(space, w_subtype, w_obj, w_times=None):
      return space.wrap(W_Repeat(space, w_obj, w_times))
  
  W_Repeat.typedef = TypeDef(
          'repeat',
          __new__  = interp2app(W_Repeat___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_Repeat.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_Repeat.next_w, unwrap_spec=['self']),
          __repr__ = interp2app(W_Repeat.repr_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that returns object over and over again.
      Runs indefinitely unless the times argument is specified.  Used
      as argument to imap() for invariant parameters to the called
      function. Also used with izip() to create an invariant part of a
      tuple record.
  
      Equivalent to :
  
      def repeat(object, times=None):
          if times is None:
              while True:
                  yield object
          else:
              for i in xrange(times):
                  yield object
      """)
  
  class W_TakeWhile(Wrappable):
  
      def __init__(self, space, w_predicate, w_iterable):
          self.space = space
          self.w_predicate = w_predicate
          self.iterable = space.iter(w_iterable)
          self.stopped = False
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.stopped:
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
  
          w_obj = self.space.next(self.iterable)  # may raise a w_StopIteration
          w_bool = self.space.call_function(self.w_predicate, w_obj)
          if not self.space.is_true(w_bool):
              self.stopped = True
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
  
          return w_obj
  
  def W_TakeWhile___new__(space, w_subtype, w_predicate, w_iterable):
      return space.wrap(W_TakeWhile(space, w_predicate, w_iterable))
  
  
  W_TakeWhile.typedef = TypeDef(
          'takewhile',
          __new__  = interp2app(W_TakeWhile___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_TakeWhile.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_TakeWhile.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that returns elements from the iterable as
      long as the predicate is true.
  
      Equivalent to :
      
      def takewhile(predicate, iterable):
          for x in iterable:
              if predicate(x):
                  yield x
              else:
                  break
      """)
  
  class W_DropWhile(Wrappable):
  
      def __init__(self, space, w_predicate, w_iterable):
          self.space = space
          self.w_predicate = w_predicate
          self.iterable = space.iter(w_iterable)
          self.started = False
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.started:
              w_obj = self.space.next(self.iterable)  # may raise w_StopIteration
          else:
              while True:
                  w_obj = self.space.next(self.iterable)  # may raise w_StopIter
                  w_bool = self.space.call_function(self.w_predicate, w_obj)
                  if not self.space.is_true(w_bool):
                      self.started = True
                      break
  
          return w_obj
  
  def W_DropWhile___new__(space, w_subtype, w_predicate, w_iterable):
      return space.wrap(W_DropWhile(space, w_predicate, w_iterable))
  
  
  W_DropWhile.typedef = TypeDef(
          'dropwhile',
          __new__  = interp2app(W_DropWhile___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_DropWhile.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_DropWhile.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that drops elements from the iterable as long
      as the predicate is true; afterwards, returns every
      element. Note, the iterator does not produce any output until the
      predicate is true, so it may have a lengthy start-up time.
  
      Equivalent to :
  
      def dropwhile(predicate, iterable):
          iterable = iter(iterable)
          for x in iterable:
              if not predicate(x):
                  yield x
                  break
          for x in iterable:
              yield x
      """)
  
  class _IFilterBase(Wrappable):
  
      def __init__(self, space, w_predicate, w_iterable):
          self.space = space
          if space.is_w(w_predicate, space.w_None):
              self.no_predicate = True
          else:
              self.no_predicate = False
              self.w_predicate = w_predicate
          self.iterable = space.iter(w_iterable)
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          while True:
              w_obj = self.space.next(self.iterable)  # may raise w_StopIteration
              if self.no_predicate:
                  pred = self.space.is_true(w_obj)
              else:
                  w_pred = self.space.call_function(self.w_predicate, w_obj)
                  pred = self.space.is_true(w_pred)
              if pred ^ self.reverse:
                  return w_obj
  
  
  class W_IFilter(_IFilterBase):
      reverse = False
  
  def W_IFilter___new__(space, w_subtype, w_predicate, w_iterable):
      return space.wrap(W_IFilter(space, w_predicate, w_iterable))
  
  W_IFilter.typedef = TypeDef(
          'ifilter',
          __new__  = interp2app(W_IFilter___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_IFilter.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_IFilter.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that filters elements from iterable returning
      only those for which the predicate is True.  If predicate is
      None, return the items that are true.
  
      Equivalent to :
  
      def ifilter:
          if predicate is None:
              predicate = bool
          for x in iterable:
              if predicate(x):
                  yield x
      """)
  
  class W_IFilterFalse(_IFilterBase):
      reverse = True
  
  def W_IFilterFalse___new__(space, w_subtype, w_predicate, w_iterable):
      return space.wrap(W_IFilterFalse(space, w_predicate, w_iterable))
  
  W_IFilterFalse.typedef = TypeDef(
          'ifilterfalse',
          __new__  = interp2app(W_IFilterFalse___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_IFilterFalse.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_IFilterFalse.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that filters elements from iterable returning
      only those for which the predicate is False.  If predicate is
      None, return the items that are false.
  
      Equivalent to :
      
      def ifilterfalse(predicate, iterable):
          if predicate is None:
              predicate = bool
          for x in iterable:
              if not predicate(x):
                  yield x
      """)
  
  class W_ISlice(Wrappable):
      def __init__(self, space, w_iterable, w_startstop, args_w):
          self.iterable = space.iter(w_iterable)
          self.space = space
  
          num_args = len(args_w)
  
          if num_args == 0:
              start = 0
              w_stop = w_startstop
          elif num_args <= 2:
              if space.is_w(w_startstop, space.w_None):
                  start = 0
              else:
                  start = space.int_w(w_startstop)
              w_stop = args_w[0]
          else:
              raise OperationError(space.w_TypeError, space.wrap("islice() takes at most 4 arguments (" + str(num_args) + " given)"))
  
          if space.is_w(w_stop, space.w_None):
              stop = -1
              stoppable = False
          else:
              stop = space.int_w(w_stop)
              stoppable = True
  
          if num_args == 2:
              w_step = args_w[1]
              if space.is_w(w_step, space.w_None):
                  step = 1
              else:
                  step = space.int_w(w_step)
          else:
              step = 1
  
          if start < 0:
              raise OperationError(space.w_ValueError, space.wrap("Indicies for islice() must be non-negative integers."))
          if stoppable and stop < 0:
              raise OperationError(space.w_ValueError, space.wrap("Stop argument must be a non-negative integer or None."))
          if step < 1:
              raise OperationError(space.w_ValueError, space.wrap("Step must be one or lager for islice()."))
  
          self.start = start
          self.stop = stop
          self.step = step
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.start >= 0:               # first call only
              consume = self.start + 1
              self.start = -1
          else:                             # all following calls
              consume = self.step
          if self.stop >= 0:
              if self.stop < consume:
                  raise OperationError(self.space.w_StopIteration,
                                       self.space.w_None)
              self.stop -= consume
          while True:
              w_obj = self.space.next(self.iterable)
              consume -= 1
              if consume <= 0:
                  return w_obj
  
  def W_ISlice___new__(space, w_subtype, w_iterable, w_startstop, args_w):
      return space.wrap(W_ISlice(space, w_iterable, w_startstop, args_w))
  
  W_ISlice.typedef = TypeDef(
          'islice',
          __new__  = interp2app(W_ISlice___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root, 'args_w']),
          __iter__ = interp2app(W_ISlice.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_ISlice.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that returns selected elements from the
      iterable.  If start is non-zero, then elements from the iterable
      are skipped until start is reached. Afterward, elements are
      returned consecutively unless step is set higher than one which
      results in items being skipped. If stop is None, then iteration
      continues until the iterator is exhausted, if at all; otherwise,
      it stops at the specified position. Unlike regular slicing,
      islice() does not support negative values for start, stop, or
      step. Can be used to extract related fields from data where the
      internal structure has been flattened (for example, a multi-line
      report may list a name field on every third line).
      """)
  
  
  class W_Chain(Wrappable):
      def __init__(self, space, args_w):
          self.space = space
          iterators_w = []
          i = 0
          for iterable_w in args_w:
              try:
                  iterator_w = space.iter(iterable_w)
              except OperationError, e:
                  if e.match(self.space, self.space.w_TypeError):
                      raise OperationError(space.w_TypeError, space.wrap("chain argument #" + str(i + 1) + " must support iteration"))
                  else:
                      raise
              else:
                  iterators_w.append(iterator_w)
  
              i += 1
  
          self.iterators_w = iterators_w
          self.current_iterator = 0
          self.num_iterators = len(iterators_w)
          self.started = False
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.current_iterator >= self.num_iterators:
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
          if not self.started:
              self.current_iterator = 0
              self.w_it = self.iterators_w[self.current_iterator]
              self.started = True
  
          while True:
              try:
                  w_obj = self.space.next(self.w_it)
              except OperationError, e:
                  if e.match(self.space, self.space.w_StopIteration):
                      self.current_iterator += 1
                      if self.current_iterator >= self.num_iterators:
                          raise OperationError(self.space.w_StopIteration, self.space.w_None)
                      else:
                          self.w_it = self.iterators_w[self.current_iterator]
                  else:
                      raise
              else:
                  break
          return w_obj
  
  def W_Chain___new__(space, w_subtype, args_w):
      return space.wrap(W_Chain(space, args_w))
  
  W_Chain.typedef = TypeDef(
          'chain',
          __new__  = interp2app(W_Chain___new__, unwrap_spec=[ObjSpace, W_Root, 'args_w']),
          __iter__ = interp2app(W_Chain.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_Chain.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that returns elements from the first iterable
      until it is exhausted, then proceeds to the next iterable, until
      all of the iterables are exhausted. Used for treating consecutive
      sequences as a single sequence.
  
      Equivalent to :
  
      def chain(*iterables):
          for it in iterables:
              for element in it:
                  yield element
      """)
  
  class W_IMap(Wrappable):
      _error_name = "imap"
  
      def __init__(self, space, w_fun, args_w):
          self.space = space
          self.identity_fun = (self.space.is_w(w_fun, space.w_None))
          self.w_fun = w_fun
  
          iterators_w = []
          i = 0
          for iterable_w in args_w:
              try:
                  iterator_w = space.iter(iterable_w)
              except OperationError, e:
                  if e.match(self.space, self.space.w_TypeError):
                      raise OperationError(space.w_TypeError, space.wrap(self._error_name + " argument #" + str(i + 1) + " must support iteration"))
                  else:
                      raise
              else:
                  iterators_w.append(iterator_w)
  
              i += 1
  
          self.iterators_w = iterators_w
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          w_objects = self.space.newtuple([self.space.next(w_it) for w_it in self.iterators_w])
          if self.identity_fun:
              return w_objects
          else:
              return self.space.call(self.w_fun, w_objects)
  
  
  def W_IMap___new__(space, w_subtype, w_fun, args_w):
      if len(args_w) == 0:
          raise OperationError(space.w_TypeError,
                    space.wrap("imap() must have at least two arguments"))
      return space.wrap(W_IMap(space, w_fun, args_w))
  
  W_IMap.typedef = TypeDef(
          'imap',
          __new__  = interp2app(W_IMap___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, 'args_w']),
          __iter__ = interp2app(W_IMap.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_IMap.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that computes the function using arguments
      from each of the iterables. If function is set to None, then
      imap() returns the arguments as a tuple. Like map() but stops
      when the shortest iterable is exhausted instead of filling in
      None for shorter iterables. The reason for the difference is that
      infinite iterator arguments are typically an error for map()
      (because the output is fully evaluated) but represent a common
      and useful way of supplying arguments to imap().
  
      Equivalent to :
  
      def imap(function, *iterables):
          iterables = map(iter, iterables)
          while True:
              args = [i.next() for i in iterables]
              if function is None:
                  yield tuple(args)
              else:
                  yield function(*args)
      
      """)
  
  
  class W_IZip(W_IMap):
      _error_name = "izip"
  
      def next_w(self):
          # argh.  izip(*args) is almost like imap(None, *args) except
          # that the former needs a special case for len(args)==0
          # while the latter just raises a TypeError in this situation.
          if len(self.iterators_w) == 0:
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
          return W_IMap.next_w(self)
  
  def W_IZip___new__(space, w_subtype, args_w):
      return space.wrap(W_IZip(space, space.w_None, args_w))
  
  W_IZip.typedef = TypeDef(
          'izip',
          __new__  = interp2app(W_IZip___new__, unwrap_spec=[ObjSpace, W_Root, 'args_w']),
          __iter__ = interp2app(W_IZip.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_IZip.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that aggregates elements from each of the
      iterables.  Like zip() except that it returns an iterator instead
      of a list. Used for lock-step iteration over several iterables at
      a time.
  
      Equivalent to :
  
      def izip(*iterables):
          iterables = map(iter, iterables)
          while iterables:
              result = [i.next() for i in iterables]
              yield tuple(result)
      """)
  
  
  class W_Cycle(Wrappable):
  
      def __init__(self, space, w_iterable):
          self.space = space
          self.saved_w = []
          self.w_iterable = space.iter(w_iterable)
          self.index = 0
          self.exhausted = False
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.exhausted:
              if not self.saved_w:
                  raise OperationError(self.space.w_StopIteration, self.space.w_None)
              try:
                  w_obj = self.saved_w[self.index]
              except IndexError:
                  self.index = 1
                  w_obj = self.saved_w[0]
              else:
                  self.index += 1
          else:
              try:
                  w_obj = self.space.next(self.w_iterable)
              except OperationError, e:
                  if e.match(self.space, self.space.w_StopIteration):
                      self.exhausted = True
                      if not self.saved_w:
                          raise
                      self.index = 1
                      w_obj = self.saved_w[0]
                  else:
                      raise
              else:
                  self.index += 1
                  self.saved_w.append(w_obj)
          return w_obj
  
  def W_Cycle___new__(space, w_subtype, w_iterable):
      return space.wrap(W_Cycle(space, w_iterable))
  
  W_Cycle.typedef = TypeDef(
          'cycle',
          __new__  = interp2app(W_Cycle___new__, unwrap_spec=[ObjSpace, W_Root, W_Root]),
          __iter__ = interp2app(W_Cycle.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_Cycle.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator returning elements from the iterable and
      saving a copy of each. When the iterable is exhausted, return
      elements from the saved copy. Repeats indefinitely.
      
      Equivalent to :
      
      def cycle(iterable):
          saved = []
          for element in iterable:
              yield element
              saved.append(element)
          while saved:
              for element in saved:
                  yield element    
      """)
  
  class W_StarMap(Wrappable):
  
      def __init__(self, space, w_fun, w_iterable):
          self.space = space
          self.w_fun = w_fun
          self.w_iterable = self.space.iter(w_iterable)
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          w_obj = self.space.next(self.w_iterable)
          if not self.space.is_true(self.space.isinstance(w_obj, self.space.w_tuple)):
              raise OperationError(self.space.w_TypeError, self.space.wrap("iterator must return a tuple"))
  
          return self.space.call(self.w_fun, w_obj)
  
  def W_StarMap___new__(space, w_subtype, w_fun, w_iterable):
      return space.wrap(W_StarMap(space, w_fun, w_iterable))
  
  W_StarMap.typedef = TypeDef(
          'starmap',
          __new__  = interp2app(W_StarMap___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_StarMap.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_StarMap.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that computes the function using arguments
      tuples obtained from the iterable. Used instead of imap() when
      argument parameters are already grouped in tuples from a single
      iterable (the data has been ``pre-zipped''). The difference
      between imap() and starmap() parallels the distinction between
      function(a,b) and function(*c).
  
      Equivalent to :
  
      def starmap(function, iterable):
          iterable = iter(iterable)
          while True:
              yield function(*iterable.next())    
      """)
  
  
  def tee(space, w_iterable, n=2):
      """Return n independent iterators from a single iterable.
      Note : once tee() has made a split, the original iterable
      should not be used anywhere else; otherwise, the iterable could get
      advanced without the tee objects being informed.
      
      Note : this member of the toolkit may require significant auxiliary
      storage (depending on how much temporary data needs to be stored).
      In general, if one iterator is going to use most or all of the
      data before the other iterator, it is faster to use list() instead
      of tee()
      
      Equivalent to :
      
      def tee(iterable, n=2):
          def gen(next, data={}, cnt=[0]):
              for i in count():
                  if i == cnt[0]:
                      item = data[i] = next()
                      cnt[0] += 1
                  else:
                      item = data.pop(i)
                  yield item
          it = iter(iterable)
          return tuple([gen(it.next) for i in range(n)])
      """
      if n < 0:
          raise OperationError(space.w_ValueError, space.wrap("n must be >= 0"))
  
      myiter = space.interpclass_w(w_iterable)
      if isinstance(myiter, W_TeeIterable):     # optimization only
          tee_state = myiter.tee_state
          iterators_w = [w_iterable] * n
          for i in range(1, n):
              iterators_w[i] = space.wrap(W_TeeIterable(space, tee_state))
      else:
          tee_state = TeeState(space, w_iterable)
          iterators_w = [space.wrap(W_TeeIterable(space, tee_state)) for x in range(n)]
      return space.newtuple(iterators_w)
  tee.unwrap_spec = [ObjSpace, W_Root, int]
  
  class TeeState(object):
      def __init__(self, space, w_iterable):
          self.space = space
          self.w_iterable = self.space.iter(w_iterable)
          self.num_saved = 0
          self.saved_w = []
  
      def get_next(self, index):
          if index >= self.num_saved:
              w_obj = self.space.next(self.w_iterable)
              self.saved_w.append(w_obj)
              self.num_saved += 1
              return w_obj
          else:
              return self.saved_w[index]
  
  class W_TeeIterable(Wrappable):
      def __init__(self, space, tee_state):
          self.space = space
          self.tee_state = tee_state
          self.index = 0
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          try:
              w_obj = self.tee_state.get_next(self.index)
              return w_obj
          finally:
              self.index += 1
  
  def W_TeeIterable___new__(space, w_subtype, w_iterable):
      # Obscure and undocumented function.  PyPy only supports w_iterable
      # being a W_TeeIterable, because the case where it is a general
      # iterable is useless and confusing as far as I can tell (as the
      # semantics are then slightly different; see the XXX in lib-python's
      # test_itertools).
      myiter = space.interp_w(W_TeeIterable, w_iterable)
      tee_state = myiter.tee_state
      return space.wrap(W_TeeIterable(space, tee_state))
  
  W_TeeIterable.typedef = TypeDef(
          '_tee',
          __new__ = interp2app(W_TeeIterable___new__, unwrap_spec=[ObjSpace,
                                                                   W_Root,
                                                                   W_Root]),
          __iter__ = interp2app(W_TeeIterable.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_TeeIterable.next_w, unwrap_spec=['self']),
          __weakref__ = make_weakref_descr(W_TeeIterable),
          )
  W_TeeIterable.typedef.acceptable_as_base_class = False
  
  
  class W_GroupBy(Wrappable):
  
      def __init__(self, space, w_iterable, w_fun):
          self.space = space
          self.w_iterable = self.space.iter(w_iterable)
          self.identity_fun = self.space.is_w(w_fun, self.space.w_None)
          self.w_fun = w_fun
          self.index = 0
          self.lookahead = False
          self.exhausted = False
          self.started = False
          # new_group - new group not started yet, next should not skip any items
          self.new_group = True 
          self.w_lookahead = self.space.w_None
          self.w_key = self.space.w_None
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.exhausted:
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
  
          if not self.new_group:
              # Consume unwanted input until we reach the next group
              try:
                  while True:
                      self.group_next(self.index)
  
              except StopIteration:
                  pass
              if self.exhausted:
                  raise OperationError(self.space.w_StopIteration, self.space.w_None)
  
          if not self.started:
              self.started = True
              try:
                  w_obj = self.space.next(self.w_iterable)
              except OperationError, e:
                  if e.match(self.space, self.space.w_StopIteration):
                      self.exhausted = True
                  raise
              else:
                  self.w_lookahead = w_obj
                  if self.identity_fun:
                      self.w_key = w_obj
                  else:
                      self.w_key = self.space.call_function(self.w_fun, w_obj)
                  self.lookahead = True
  
          self.new_group = False
          w_iterator = self.space.wrap(W_GroupByIterator(self.space, self.index, self))
          return self.space.newtuple([self.w_key, w_iterator])
  
      def group_next(self, group_index):
          if group_index < self.index:
              raise StopIteration
          else:
              if self.lookahead:
                  self.lookahead = False
                  return self.w_lookahead
  
              try:
                  w_obj = self.space.next(self.w_iterable)
              except OperationError, e:
                  if e.match(self.space, self.space.w_StopIteration):
                      self.exhausted = True
                      raise StopIteration
                  else:
                      raise
              else:
                  if self.identity_fun:
                      w_new_key = w_obj
                  else:
                      w_new_key = self.space.call_function(self.w_fun, w_obj)
                  if self.space.eq_w(self.w_key, w_new_key):
                      return w_obj
                  else:
                      self.index += 1
                      self.w_lookahead = w_obj
                      self.w_key = w_new_key
                      self.lookahead = True
                      self.new_group = True #new group
                      raise StopIteration
  
  def W_GroupBy___new__(space, w_subtype, w_iterable, w_key=None):
      return space.wrap(W_GroupBy(space, w_iterable, w_key))
  
  W_GroupBy.typedef = TypeDef(
          'groupby',
          __new__  = interp2app(W_GroupBy___new__, unwrap_spec=[ObjSpace, W_Root, W_Root, W_Root]),
          __iter__ = interp2app(W_GroupBy.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_GroupBy.next_w, unwrap_spec=['self']),
          __doc__  = """Make an iterator that returns consecutive keys and groups from the
      iterable. The key is a function computing a key value for each
      element. If not specified or is None, key defaults to an identity
      function and returns the element unchanged. Generally, the
      iterable needs to already be sorted on the same key function.
  
      The returned group is itself an iterator that shares the
      underlying iterable with groupby(). Because the source is shared,
      when the groupby object is advanced, the previous group is no
      longer visible. So, if that data is needed later, it should be
      stored as a list:
  
         groups = []
         uniquekeys = []
         for k, g in groupby(data, keyfunc):
             groups.append(list(g))      # Store group iterator as a list
             uniquekeys.append(k)
      """)
  
  class W_GroupByIterator(Wrappable):
      def __init__(self, space, index, groupby):
          self.space = space
          self.index = index
          self.groupby = groupby
          self.exhausted = False
  
      def iter_w(self):
          return self.space.wrap(self)
  
      def next_w(self):
          if self.exhausted:
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
  
          try:
              w_obj = self.groupby.group_next(self.index)
          except StopIteration:
              self.exhausted = True
              raise OperationError(self.space.w_StopIteration, self.space.w_None)
          else:
              return w_obj
  
  W_GroupByIterator.typedef = TypeDef(
          '_groupby',
          __iter__ = interp2app(W_GroupByIterator.iter_w, unwrap_spec=['self']),
          next     = interp2app(W_GroupByIterator.next_w, unwrap_spec=['self']))
  W_GroupByIterator.typedef.acceptable_as_base_class = False
  