Home | History | Annotate | Download | only in classes
      1 """Example of a generator: re-implement the built-in range function
      2 without actually constructing the list of values.
      3 
      4 OldStyleRange is coded in the way required to work in a 'for' loop before
      5 iterators were introduced into the language; using __getitem__ and __len__ .
      6 
      7 """
      8 def handleargs(arglist):
      9     """Take list of arguments and extract/create proper start, stop, and step
     10     values and return in a tuple"""
     11     try:
     12         if len(arglist) == 1:
     13             return 0, int(arglist[0]), 1
     14         elif len(arglist) == 2:
     15             return int(arglist[0]), int(arglist[1]), 1
     16         elif len(arglist) == 3:
     17             if arglist[2] == 0:
     18                 raise ValueError("step argument must not be zero")
     19             return tuple(int(x) for x in arglist)
     20         else:
     21             raise TypeError("range() accepts 1-3 arguments, given", len(arglist))
     22     except TypeError:
     23         raise TypeError("range() arguments must be numbers or strings "
     24         "representing numbers")
     25 
     26 def genrange(*a):
     27     """Function to implement 'range' as a generator"""
     28     start, stop, step = handleargs(a)
     29     value = start
     30     while value < stop:
     31         yield value
     32         value += step
     33 
     34 class oldrange:
     35     """Class implementing a range object.
     36     To the user the instances feel like immutable sequences
     37     (and you can't concatenate or slice them)
     38 
     39     Done using the old way (pre-iterators; __len__ and __getitem__) to have an
     40     object be used by a 'for' loop.
     41 
     42     """
     43 
     44     def __init__(self, *a):
     45         """ Initialize start, stop, and step values along with calculating the
     46         nubmer of values (what __len__ will return) in the range"""
     47         self.start, self.stop, self.step = handleargs(a)
     48         self.len = max(0, (self.stop - self.start) // self.step)
     49 
     50     def __repr__(self):
     51         """implement repr(x) which is also used by print"""
     52         return 'range(%r, %r, %r)' % (self.start, self.stop, self.step)
     53 
     54     def __len__(self):
     55         """implement len(x)"""
     56         return self.len
     57 
     58     def __getitem__(self, i):
     59         """implement x[i]"""
     60         if 0 <= i <= self.len:
     61             return self.start + self.step * i
     62         else:
     63             raise IndexError, 'range[i] index out of range'
     64 
     65 
     66 def test():
     67     import time, __builtin__
     68     #Just a quick sanity check

     69     correct_result = __builtin__.range(5, 100, 3)
     70     oldrange_result = list(oldrange(5, 100, 3))
     71     genrange_result = list(genrange(5, 100, 3))
     72     if genrange_result != correct_result or oldrange_result != correct_result:
     73         raise Exception("error in implementation:\ncorrect   = %s"
     74                          "\nold-style = %s\ngenerator = %s" %
     75                          (correct_result, oldrange_result, genrange_result))
     76     print "Timings for range(1000):"
     77     t1 = time.time()
     78     for i in oldrange(1000):
     79         pass
     80     t2 = time.time()
     81     for i in genrange(1000):
     82         pass
     83     t3 = time.time()
     84     for i in __builtin__.range(1000):
     85         pass
     86     t4 = time.time()
     87     print t2-t1, 'sec (old-style class)'
     88     print t3-t2, 'sec (generator)'
     89     print t4-t3, 'sec (built-in)'
     90 
     91 
     92 if __name__ == '__main__':
     93     test()
     94