blob: af4467e1374db3e4b8c33ff28ed8b079db6d3a5e [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:
Armin Rigof5b3e362006-02-11 21:32:43 +000058 # note: this is an internal undocumented API,
59 # don't rely on it in your own programs
60 return obj.__length_hint__()
Raymond Hettinger6b27cda2005-09-24 21:23:05 +000061 except AttributeError:
62 raise TypeError
63
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000064class TestInvariantWithoutMutations(unittest.TestCase):
65
66 def test_invariant(self):
Tim Peters27f88362004-07-08 04:22:35 +000067 it = self.it
68 for i in reversed(xrange(1, n+1)):
69 self.assertEqual(len(it), i)
70 it.next()
71 self.assertEqual(len(it), 0)
72 self.assertRaises(StopIteration, it.next)
73 self.assertEqual(len(it), 0)
Raymond Hettinger7892b1c2004-04-12 18:10:01 +000074
75class TestTemporarilyImmutable(TestInvariantWithoutMutations):
76
77 def test_immutable_during_iteration(self):
78 # objects such as deques, sets, and dictionaries enforce
79 # length immutability during iteration
80
81 it = self.it
82 self.assertEqual(len(it), n)
83 it.next()
84 self.assertEqual(len(it), n-1)
85 self.mutate()
86 self.assertRaises(RuntimeError, it.next)
87 self.assertEqual(len(it), 0)
88
89## ------- Concrete Type Tests -------
90
91class TestRepeat(TestInvariantWithoutMutations):
92
93 def setUp(self):
94 self.it = repeat(None, n)
95
96 def test_no_len_for_infinite_repeat(self):
97 # The repeat() object can also be infinite
98 self.assertRaises(TypeError, len, repeat(None))
99
100class TestXrange(TestInvariantWithoutMutations):
101
102 def setUp(self):
103 self.it = iter(xrange(n))
104
105class TestXrangeCustomReversed(TestInvariantWithoutMutations):
106
107 def setUp(self):
108 self.it = reversed(xrange(n))
109
110class TestTuple(TestInvariantWithoutMutations):
111
112 def setUp(self):
113 self.it = iter(tuple(xrange(n)))
114
115## ------- Types that should not be mutated during iteration -------
116
117class TestDeque(TestTemporarilyImmutable):
118
119 def setUp(self):
120 d = deque(xrange(n))
121 self.it = iter(d)
122 self.mutate = d.pop
123
124class TestDequeReversed(TestTemporarilyImmutable):
125
126 def setUp(self):
127 d = deque(xrange(n))
128 self.it = reversed(d)
129 self.mutate = d.pop
130
131class TestDictKeys(TestTemporarilyImmutable):
132
133 def setUp(self):
134 d = dict.fromkeys(xrange(n))
135 self.it = iter(d)
136 self.mutate = d.popitem
137
138class TestDictItems(TestTemporarilyImmutable):
139
140 def setUp(self):
141 d = dict.fromkeys(xrange(n))
142 self.it = d.iteritems()
143 self.mutate = d.popitem
144
145class TestDictValues(TestTemporarilyImmutable):
146
147 def setUp(self):
148 d = dict.fromkeys(xrange(n))
149 self.it = d.itervalues()
150 self.mutate = d.popitem
151
152class TestSet(TestTemporarilyImmutable):
153
154 def setUp(self):
155 d = set(xrange(n))
156 self.it = iter(d)
157 self.mutate = d.pop
158
159## ------- Types that can mutate during iteration -------
160
161class TestList(TestInvariantWithoutMutations):
162
163 def setUp(self):
164 self.it = iter(range(n))
165
166 def test_mutation(self):
167 d = range(n)
168 it = iter(d)
169 it.next()
170 it.next()
171 self.assertEqual(len(it), n-2)
172 d.append(n)
173 self.assertEqual(len(it), n-1) # grow with append
174 d[1:] = []
175 self.assertEqual(len(it), 0)
176 self.assertEqual(list(it), [])
177 d.extend(xrange(20))
178 self.assertEqual(len(it), 0)
179
180class TestListReversed(TestInvariantWithoutMutations):
181
182 def setUp(self):
183 self.it = reversed(range(n))
184
185 def test_mutation(self):
186 d = range(n)
187 it = reversed(d)
188 it.next()
189 it.next()
190 self.assertEqual(len(it), n-2)
191 d.append(n)
192 self.assertEqual(len(it), n-2) # ignore append
193 d[1:] = []
194 self.assertEqual(len(it), 0)
195 self.assertEqual(list(it), []) # confirm invariant
196 d.extend(xrange(20))
197 self.assertEqual(len(it), 0)
198
199class TestSeqIter(TestInvariantWithoutMutations):
200
201 def setUp(self):
202 self.it = iter(UserList(range(n)))
203
204 def test_mutation(self):
205 d = UserList(range(n))
206 it = iter(d)
207 it.next()
208 it.next()
209 self.assertEqual(len(it), n-2)
210 d.append(n)
211 self.assertEqual(len(it), n-1) # grow with append
212 d[1:] = []
213 self.assertEqual(len(it), 0)
214 self.assertEqual(list(it), [])
215 d.extend(xrange(20))
216 self.assertEqual(len(it), 0)
217
218class TestSeqIterReversed(TestInvariantWithoutMutations):
219
220 def setUp(self):
221 self.it = reversed(UserList(range(n)))
222
223 def test_mutation(self):
224 d = UserList(range(n))
225 it = reversed(d)
226 it.next()
227 it.next()
228 self.assertEqual(len(it), n-2)
229 d.append(n)
230 self.assertEqual(len(it), n-2) # ignore append
231 d[1:] = []
232 self.assertEqual(len(it), 0)
233 self.assertEqual(list(it), []) # confirm invariant
234 d.extend(xrange(20))
235 self.assertEqual(len(it), 0)
236
237
Georg Brandlf102fc52006-07-27 15:05:36 +0000238def test_main():
Raymond Hettinger7892b1c2004-04-12 18:10:01 +0000239 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)
Georg Brandlf102fc52006-07-27 15:05:36 +0000256
257if __name__ == "__main__":
258 test_main()