blob: 1770de2c0ad383650c1d71eac200a1faec959781 [file] [log] [blame]
Raymond Hettinger7892b1c2004-04-12 18:10:01 +00001""" Test Iterator Length Transparency
2
3Some functions or methods which accept general iterable arguments have
4optional, more efficient code paths if they know how many items to expect.
5For instance, map(func, iterable), will pre-allocate the exact amount of
6space required whenever the iterable can report its length.
7
8The desired invariant is: len(it)==len(list(it)).
9
10A complication is that an iterable and iterator can be the same object. To
11maintain the invariant, an iterator needs to dynamically update its length.
12For instance, an iterable such as xrange(10) always reports its length as ten,
13but it=iter(xrange(10)) starts at ten, and then goes to nine after it.next().
14Having this capability means that map() can ignore the distinction between
15map(func, iterable) and map(func, iter(iterable)).
16
17When the iterable is immutable, the implementation can straight-forwardly
18report the original length minus the cumulative number of calls to next().
19This is the case for tuples, xrange objects, and itertools.repeat().
20
21Some containers become temporarily immutable during iteration. This includes
22dicts, sets, and collections.deque. Their implementation is equally simple
23though they need to permantently set their length to zero whenever there is
24an attempt to iterate after a length mutation.
25
26The situation slightly more involved whenever an object allows length mutation
27during iteration. Lists and sequence iterators are dynanamically updatable.
28So, if a list is extended during iteration, the iterator will continue through
29the new items. If it shrinks to a point before the most recent iteration,
30then no further items are available and the length is reported at zero.
31
32Reversed objects can also be wrapped around mutable objects; however, any
33appends after the current position are ignored. Any other approach leads
34to confusion and possibly returning the same item more than once.
35
36The iterators not listed above, such as enumerate and the other itertools,
37are not length transparent because they have no way to distinguish between
38iterables that report static length and iterators whose length changes with
39each call (i.e. the difference between enumerate('abc') and
40enumerate(iter('abc')).
41
42"""
43
44import unittest
45from test import test_support
Raymond Hettinger6b27cda2005-09-24 21:23:05 +000046from itertools import repeat
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000047from collections import deque
48from UserList import UserList
Raymond Hettinger6b27cda2005-09-24 21:23:05 +000049from __builtin__ import len as _len
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000050
51n = 10
52
Raymond Hettinger6b27cda2005-09-24 21:23:05 +000053def len(obj):
54 try:
55 return _len(obj)
56 except TypeError:
57 try:
58 return obj._length_cue()
59 except AttributeError:
60 raise TypeError
61
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000062class TestInvariantWithoutMutations(unittest.TestCase):
63
64 def test_invariant(self):
Tim Peters27f88362004-07-08 04:22:35 +000065 it = self.it
66 for i in reversed(xrange(1, n+1)):
67 self.assertEqual(len(it), i)
68 it.next()
69 self.assertEqual(len(it), 0)
70 self.assertRaises(StopIteration, it.next)
71 self.assertEqual(len(it), 0)
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000072
73class TestTemporarilyImmutable(TestInvariantWithoutMutations):
74
75 def test_immutable_during_iteration(self):
76 # objects such as deques, sets, and dictionaries enforce
77 # length immutability during iteration
78
79 it = self.it
80 self.assertEqual(len(it), n)
81 it.next()
82 self.assertEqual(len(it), n-1)
83 self.mutate()
84 self.assertRaises(RuntimeError, it.next)
85 self.assertEqual(len(it), 0)
86
87## ------- Concrete Type Tests -------
88
89class TestRepeat(TestInvariantWithoutMutations):
90
91 def setUp(self):
92 self.it = repeat(None, n)
93
94 def test_no_len_for_infinite_repeat(self):
95 # The repeat() object can also be infinite
96 self.assertRaises(TypeError, len, repeat(None))
97
98class TestXrange(TestInvariantWithoutMutations):
99
100 def setUp(self):
101 self.it = iter(xrange(n))
102
103class TestXrangeCustomReversed(TestInvariantWithoutMutations):
104
105 def setUp(self):
106 self.it = reversed(xrange(n))
107
108class TestTuple(TestInvariantWithoutMutations):
109
110 def setUp(self):
111 self.it = iter(tuple(xrange(n)))
112
113## ------- Types that should not be mutated during iteration -------
114
115class TestDeque(TestTemporarilyImmutable):
116
117 def setUp(self):
118 d = deque(xrange(n))
119 self.it = iter(d)
120 self.mutate = d.pop
121
122class TestDequeReversed(TestTemporarilyImmutable):
123
124 def setUp(self):
125 d = deque(xrange(n))
126 self.it = reversed(d)
127 self.mutate = d.pop
128
129class TestDictKeys(TestTemporarilyImmutable):
130
131 def setUp(self):
132 d = dict.fromkeys(xrange(n))
133 self.it = iter(d)
134 self.mutate = d.popitem
135
136class TestDictItems(TestTemporarilyImmutable):
137
138 def setUp(self):
139 d = dict.fromkeys(xrange(n))
140 self.it = d.iteritems()
141 self.mutate = d.popitem
142
143class TestDictValues(TestTemporarilyImmutable):
144
145 def setUp(self):
146 d = dict.fromkeys(xrange(n))
147 self.it = d.itervalues()
148 self.mutate = d.popitem
149
150class TestSet(TestTemporarilyImmutable):
151
152 def setUp(self):
153 d = set(xrange(n))
154 self.it = iter(d)
155 self.mutate = d.pop
156
157## ------- Types that can mutate during iteration -------
158
159class TestList(TestInvariantWithoutMutations):
160
161 def setUp(self):
162 self.it = iter(range(n))
163
164 def test_mutation(self):
165 d = range(n)
166 it = iter(d)
167 it.next()
168 it.next()
169 self.assertEqual(len(it), n-2)
170 d.append(n)
171 self.assertEqual(len(it), n-1) # grow with append
172 d[1:] = []
173 self.assertEqual(len(it), 0)
174 self.assertEqual(list(it), [])
175 d.extend(xrange(20))
176 self.assertEqual(len(it), 0)
177
178class TestListReversed(TestInvariantWithoutMutations):
179
180 def setUp(self):
181 self.it = reversed(range(n))
182
183 def test_mutation(self):
184 d = range(n)
185 it = reversed(d)
186 it.next()
187 it.next()
188 self.assertEqual(len(it), n-2)
189 d.append(n)
190 self.assertEqual(len(it), n-2) # ignore append
191 d[1:] = []
192 self.assertEqual(len(it), 0)
193 self.assertEqual(list(it), []) # confirm invariant
194 d.extend(xrange(20))
195 self.assertEqual(len(it), 0)
196
197class TestSeqIter(TestInvariantWithoutMutations):
198
199 def setUp(self):
200 self.it = iter(UserList(range(n)))
201
202 def test_mutation(self):
203 d = UserList(range(n))
204 it = iter(d)
205 it.next()
206 it.next()
207 self.assertEqual(len(it), n-2)
208 d.append(n)
209 self.assertEqual(len(it), n-1) # grow with append
210 d[1:] = []
211 self.assertEqual(len(it), 0)
212 self.assertEqual(list(it), [])
213 d.extend(xrange(20))
214 self.assertEqual(len(it), 0)
215
216class TestSeqIterReversed(TestInvariantWithoutMutations):
217
218 def setUp(self):
219 self.it = reversed(UserList(range(n)))
220
221 def test_mutation(self):
222 d = UserList(range(n))
223 it = reversed(d)
224 it.next()
225 it.next()
226 self.assertEqual(len(it), n-2)
227 d.append(n)
228 self.assertEqual(len(it), n-2) # ignore append
229 d[1:] = []
230 self.assertEqual(len(it), 0)
231 self.assertEqual(list(it), []) # confirm invariant
232 d.extend(xrange(20))
233 self.assertEqual(len(it), 0)
234
235
236
237if __name__ == "__main__":
238
239 unittests = [
240 TestRepeat,
241 TestXrange,
242 TestXrangeCustomReversed,
243 TestTuple,
244 TestDeque,
245 TestDequeReversed,
246 TestDictKeys,
247 TestDictItems,
248 TestDictValues,
249 TestSet,
250 TestList,
251 TestListReversed,
252 TestSeqIter,
253 TestSeqIterReversed,
254 ]
255 test_support.run_unittest(*unittests)