1"""Example of a generator: re-implement the built-in range function
2without actually constructing the list of values.
3
4OldStyleRange is coded in the way required to work in a 'for' loop before
5iterators were introduced into the language; using __getitem__ and __len__ .
6
7"""
8def 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
26def 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
34class 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
66def 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
92if __name__ == '__main__':
93    test()
94