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()
|
---|