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