Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 1 | """ Test Iterator Length Transparency |
| 2 | |
| 3 | Some functions or methods which accept general iterable arguments have |
| 4 | optional, more efficient code paths if they know how many items to expect. |
| 5 | For instance, map(func, iterable), will pre-allocate the exact amount of |
| 6 | space required whenever the iterable can report its length. |
| 7 | |
| 8 | The desired invariant is: len(it)==len(list(it)). |
| 9 | |
| 10 | A complication is that an iterable and iterator can be the same object. To |
| 11 | maintain the invariant, an iterator needs to dynamically update its length. |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 12 | For instance, an iterable such as range(10) always reports its length as ten, |
| 13 | but it=iter(range(10)) starts at ten, and then goes to nine after next(it). |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 14 | Having this capability means that map() can ignore the distinction between |
| 15 | map(func, iterable) and map(func, iter(iterable)). |
| 16 | |
| 17 | When the iterable is immutable, the implementation can straight-forwardly |
| 18 | report the original length minus the cumulative number of calls to next(). |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 19 | This is the case for tuples, range objects, and itertools.repeat(). |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 20 | |
| 21 | Some containers become temporarily immutable during iteration. This includes |
| 22 | dicts, sets, and collections.deque. Their implementation is equally simple |
Ezio Melotti | 1392500 | 2011-03-16 11:05:33 +0200 | [diff] [blame] | 23 | though they need to permanently set their length to zero whenever there is |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 24 | an attempt to iterate after a length mutation. |
| 25 | |
| 26 | The situation slightly more involved whenever an object allows length mutation |
Ezio Melotti | 1392500 | 2011-03-16 11:05:33 +0200 | [diff] [blame] | 27 | during iteration. Lists and sequence iterators are dynamically updatable. |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 28 | So, if a list is extended during iteration, the iterator will continue through |
| 29 | the new items. If it shrinks to a point before the most recent iteration, |
| 30 | then no further items are available and the length is reported at zero. |
| 31 | |
| 32 | Reversed objects can also be wrapped around mutable objects; however, any |
| 33 | appends after the current position are ignored. Any other approach leads |
| 34 | to confusion and possibly returning the same item more than once. |
| 35 | |
| 36 | The iterators not listed above, such as enumerate and the other itertools, |
| 37 | are not length transparent because they have no way to distinguish between |
| 38 | iterables that report static length and iterators whose length changes with |
| 39 | each call (i.e. the difference between enumerate('abc') and |
| 40 | enumerate(iter('abc')). |
| 41 | |
| 42 | """ |
| 43 | |
| 44 | import unittest |
Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 45 | from test import support |
Raymond Hettinger | 6b27cda | 2005-09-24 21:23:05 +0000 | [diff] [blame] | 46 | from itertools import repeat |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 47 | from collections import deque |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 48 | from operator import length_hint |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 49 | |
| 50 | n = 10 |
| 51 | |
Raymond Hettinger | 6b27cda | 2005-09-24 21:23:05 +0000 | [diff] [blame] | 52 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 53 | class TestInvariantWithoutMutations: |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 54 | |
| 55 | def test_invariant(self): |
Tim Peters | 27f8836 | 2004-07-08 04:22:35 +0000 | [diff] [blame] | 56 | it = self.it |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 57 | for i in reversed(range(1, n+1)): |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 58 | self.assertEqual(length_hint(it), i) |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 59 | next(it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 60 | self.assertEqual(length_hint(it), 0) |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 61 | self.assertRaises(StopIteration, next, it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 62 | self.assertEqual(length_hint(it), 0) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 63 | |
| 64 | class TestTemporarilyImmutable(TestInvariantWithoutMutations): |
| 65 | |
| 66 | def test_immutable_during_iteration(self): |
| 67 | # objects such as deques, sets, and dictionaries enforce |
| 68 | # length immutability during iteration |
| 69 | |
| 70 | it = self.it |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 71 | self.assertEqual(length_hint(it), n) |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 72 | next(it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 73 | self.assertEqual(length_hint(it), n-1) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 74 | self.mutate() |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 75 | self.assertRaises(RuntimeError, next, it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 76 | self.assertEqual(length_hint(it), 0) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 77 | |
| 78 | ## ------- Concrete Type Tests ------- |
| 79 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 80 | class TestRepeat(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 81 | |
| 82 | def setUp(self): |
| 83 | self.it = repeat(None, n) |
| 84 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 85 | class TestXrange(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 86 | |
| 87 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 88 | self.it = iter(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 89 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 90 | class TestXrangeCustomReversed(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 91 | |
| 92 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 93 | self.it = reversed(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 94 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 95 | class TestTuple(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 96 | |
| 97 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 98 | self.it = iter(tuple(range(n))) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 99 | |
| 100 | ## ------- Types that should not be mutated during iteration ------- |
| 101 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 102 | class TestDeque(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 103 | |
| 104 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 105 | d = deque(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 106 | self.it = iter(d) |
| 107 | self.mutate = d.pop |
| 108 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 109 | class TestDequeReversed(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 110 | |
| 111 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 112 | d = deque(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 113 | self.it = reversed(d) |
| 114 | self.mutate = d.pop |
| 115 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 116 | class TestDictKeys(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 117 | |
| 118 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 119 | d = dict.fromkeys(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 120 | self.it = iter(d) |
| 121 | self.mutate = d.popitem |
| 122 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 123 | class TestDictItems(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 124 | |
| 125 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 126 | d = dict.fromkeys(range(n)) |
Brett Cannon | eb6b0ee | 2007-02-22 04:45:13 +0000 | [diff] [blame] | 127 | self.it = iter(d.items()) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 128 | self.mutate = d.popitem |
| 129 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 130 | class TestDictValues(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 131 | |
| 132 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 133 | d = dict.fromkeys(range(n)) |
Brett Cannon | eb6b0ee | 2007-02-22 04:45:13 +0000 | [diff] [blame] | 134 | self.it = iter(d.values()) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 135 | self.mutate = d.popitem |
| 136 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 137 | class TestSet(TestTemporarilyImmutable, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 138 | |
| 139 | def setUp(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 140 | d = set(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 141 | self.it = iter(d) |
| 142 | self.mutate = d.pop |
| 143 | |
| 144 | ## ------- Types that can mutate during iteration ------- |
| 145 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 146 | class TestList(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 147 | |
| 148 | def setUp(self): |
| 149 | self.it = iter(range(n)) |
| 150 | |
| 151 | def test_mutation(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 152 | d = list(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 153 | it = iter(d) |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 154 | next(it) |
| 155 | next(it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 156 | self.assertEqual(length_hint(it), n - 2) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 157 | d.append(n) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 158 | self.assertEqual(length_hint(it), n - 1) # grow with append |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 159 | d[1:] = [] |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 160 | self.assertEqual(length_hint(it), 0) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 161 | self.assertEqual(list(it), []) |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 162 | d.extend(range(20)) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 163 | self.assertEqual(length_hint(it), 0) |
| 164 | |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 165 | |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 166 | class TestListReversed(TestInvariantWithoutMutations, unittest.TestCase): |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 167 | |
| 168 | def setUp(self): |
| 169 | self.it = reversed(range(n)) |
| 170 | |
| 171 | def test_mutation(self): |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 172 | d = list(range(n)) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 173 | it = reversed(d) |
Georg Brandl | a18af4e | 2007-04-21 15:47:16 +0000 | [diff] [blame] | 174 | next(it) |
| 175 | next(it) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 176 | self.assertEqual(length_hint(it), n - 2) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 177 | d.append(n) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 178 | self.assertEqual(length_hint(it), n - 2) # ignore append |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 179 | d[1:] = [] |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 180 | self.assertEqual(length_hint(it), 0) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 181 | self.assertEqual(list(it), []) # confirm invariant |
Guido van Rossum | 805365e | 2007-05-07 22:24:25 +0000 | [diff] [blame] | 182 | d.extend(range(20)) |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 183 | self.assertEqual(length_hint(it), 0) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 184 | |
Raymond Hettinger | e836423 | 2009-02-02 22:55:09 +0000 | [diff] [blame] | 185 | ## -- Check to make sure exceptions are not suppressed by __length_hint__() |
| 186 | |
| 187 | |
| 188 | class BadLen(object): |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 189 | def __iter__(self): |
| 190 | return iter(range(10)) |
| 191 | |
Raymond Hettinger | e836423 | 2009-02-02 22:55:09 +0000 | [diff] [blame] | 192 | def __len__(self): |
| 193 | raise RuntimeError('hello') |
| 194 | |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 195 | |
Raymond Hettinger | e836423 | 2009-02-02 22:55:09 +0000 | [diff] [blame] | 196 | class BadLengthHint(object): |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 197 | def __iter__(self): |
| 198 | return iter(range(10)) |
| 199 | |
Raymond Hettinger | e836423 | 2009-02-02 22:55:09 +0000 | [diff] [blame] | 200 | def __length_hint__(self): |
| 201 | raise RuntimeError('hello') |
| 202 | |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 203 | |
Raymond Hettinger | 5d65412 | 2009-02-03 02:12:10 +0000 | [diff] [blame] | 204 | class NoneLengthHint(object): |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 205 | def __iter__(self): |
| 206 | return iter(range(10)) |
| 207 | |
Raymond Hettinger | 5d65412 | 2009-02-03 02:12:10 +0000 | [diff] [blame] | 208 | def __length_hint__(self): |
Armin Ronacher | aa9a79d | 2012-10-06 14:03:24 +0200 | [diff] [blame] | 209 | return NotImplemented |
| 210 | |
Raymond Hettinger | 5d65412 | 2009-02-03 02:12:10 +0000 | [diff] [blame] | 211 | |
Raymond Hettinger | e836423 | 2009-02-02 22:55:09 +0000 | [diff] [blame] | 212 | class TestLengthHintExceptions(unittest.TestCase): |
| 213 | |
| 214 | def test_issue1242657(self): |
| 215 | self.assertRaises(RuntimeError, list, BadLen()) |
| 216 | self.assertRaises(RuntimeError, list, BadLengthHint()) |
| 217 | self.assertRaises(RuntimeError, [].extend, BadLen()) |
| 218 | self.assertRaises(RuntimeError, [].extend, BadLengthHint()) |
| 219 | b = bytearray(range(10)) |
| 220 | self.assertRaises(RuntimeError, b.extend, BadLen()) |
| 221 | self.assertRaises(RuntimeError, b.extend, BadLengthHint()) |
Raymond Hettinger | 7892b1c | 2004-04-12 18:10:01 +0000 | [diff] [blame] | 222 | |
Raymond Hettinger | 5d65412 | 2009-02-03 02:12:10 +0000 | [diff] [blame] | 223 | def test_invalid_hint(self): |
| 224 | # Make sure an invalid result doesn't muck-up the works |
| 225 | self.assertEqual(list(NoneLengthHint()), list(range(10))) |
| 226 | |
| 227 | |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 228 | if __name__ == "__main__": |
Ezio Melotti | d13c008 | 2013-04-17 04:34:05 +0300 | [diff] [blame] | 229 | unittest.main() |