17db96d56Sopenharmony_ci""" Test Iterator Length Transparency 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciSome functions or methods which accept general iterable arguments have 47db96d56Sopenharmony_cioptional, more efficient code paths if they know how many items to expect. 57db96d56Sopenharmony_ciFor instance, map(func, iterable), will pre-allocate the exact amount of 67db96d56Sopenharmony_cispace required whenever the iterable can report its length. 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ciThe desired invariant is: len(it)==len(list(it)). 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ciA complication is that an iterable and iterator can be the same object. To 117db96d56Sopenharmony_cimaintain the invariant, an iterator needs to dynamically update its length. 127db96d56Sopenharmony_ciFor instance, an iterable such as range(10) always reports its length as ten, 137db96d56Sopenharmony_cibut it=iter(range(10)) starts at ten, and then goes to nine after next(it). 147db96d56Sopenharmony_ciHaving this capability means that map() can ignore the distinction between 157db96d56Sopenharmony_cimap(func, iterable) and map(func, iter(iterable)). 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ciWhen the iterable is immutable, the implementation can straight-forwardly 187db96d56Sopenharmony_cireport the original length minus the cumulative number of calls to next(). 197db96d56Sopenharmony_ciThis is the case for tuples, range objects, and itertools.repeat(). 207db96d56Sopenharmony_ci 217db96d56Sopenharmony_ciSome containers become temporarily immutable during iteration. This includes 227db96d56Sopenharmony_cidicts, sets, and collections.deque. Their implementation is equally simple 237db96d56Sopenharmony_cithough they need to permanently set their length to zero whenever there is 247db96d56Sopenharmony_cian attempt to iterate after a length mutation. 257db96d56Sopenharmony_ci 267db96d56Sopenharmony_ciThe situation slightly more involved whenever an object allows length mutation 277db96d56Sopenharmony_ciduring iteration. Lists and sequence iterators are dynamically updatable. 287db96d56Sopenharmony_ciSo, if a list is extended during iteration, the iterator will continue through 297db96d56Sopenharmony_cithe new items. If it shrinks to a point before the most recent iteration, 307db96d56Sopenharmony_cithen no further items are available and the length is reported at zero. 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_ciReversed objects can also be wrapped around mutable objects; however, any 337db96d56Sopenharmony_ciappends after the current position are ignored. Any other approach leads 347db96d56Sopenharmony_cito confusion and possibly returning the same item more than once. 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ciThe iterators not listed above, such as enumerate and the other itertools, 377db96d56Sopenharmony_ciare not length transparent because they have no way to distinguish between 387db96d56Sopenharmony_ciiterables that report static length and iterators whose length changes with 397db96d56Sopenharmony_cieach call (i.e. the difference between enumerate('abc') and 407db96d56Sopenharmony_cienumerate(iter('abc')). 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci""" 437db96d56Sopenharmony_ci 447db96d56Sopenharmony_ciimport unittest 457db96d56Sopenharmony_cifrom itertools import repeat 467db96d56Sopenharmony_cifrom collections import deque 477db96d56Sopenharmony_cifrom operator import length_hint 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_cin = 10 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ciclass TestInvariantWithoutMutations: 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_ci def test_invariant(self): 557db96d56Sopenharmony_ci it = self.it 567db96d56Sopenharmony_ci for i in reversed(range(1, n+1)): 577db96d56Sopenharmony_ci self.assertEqual(length_hint(it), i) 587db96d56Sopenharmony_ci next(it) 597db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 607db96d56Sopenharmony_ci self.assertRaises(StopIteration, next, it) 617db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ciclass TestTemporarilyImmutable(TestInvariantWithoutMutations): 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci def test_immutable_during_iteration(self): 667db96d56Sopenharmony_ci # objects such as deques, sets, and dictionaries enforce 677db96d56Sopenharmony_ci # length immutability during iteration 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci it = self.it 707db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n) 717db96d56Sopenharmony_ci next(it) 727db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n-1) 737db96d56Sopenharmony_ci self.mutate() 747db96d56Sopenharmony_ci self.assertRaises(RuntimeError, next, it) 757db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 767db96d56Sopenharmony_ci 777db96d56Sopenharmony_ci## ------- Concrete Type Tests ------- 787db96d56Sopenharmony_ci 797db96d56Sopenharmony_ciclass TestRepeat(TestInvariantWithoutMutations, unittest.TestCase): 807db96d56Sopenharmony_ci 817db96d56Sopenharmony_ci def setUp(self): 827db96d56Sopenharmony_ci self.it = repeat(None, n) 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ciclass TestXrange(TestInvariantWithoutMutations, unittest.TestCase): 857db96d56Sopenharmony_ci 867db96d56Sopenharmony_ci def setUp(self): 877db96d56Sopenharmony_ci self.it = iter(range(n)) 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ciclass TestXrangeCustomReversed(TestInvariantWithoutMutations, unittest.TestCase): 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci def setUp(self): 927db96d56Sopenharmony_ci self.it = reversed(range(n)) 937db96d56Sopenharmony_ci 947db96d56Sopenharmony_ciclass TestTuple(TestInvariantWithoutMutations, unittest.TestCase): 957db96d56Sopenharmony_ci 967db96d56Sopenharmony_ci def setUp(self): 977db96d56Sopenharmony_ci self.it = iter(tuple(range(n))) 987db96d56Sopenharmony_ci 997db96d56Sopenharmony_ci## ------- Types that should not be mutated during iteration ------- 1007db96d56Sopenharmony_ci 1017db96d56Sopenharmony_ciclass TestDeque(TestTemporarilyImmutable, unittest.TestCase): 1027db96d56Sopenharmony_ci 1037db96d56Sopenharmony_ci def setUp(self): 1047db96d56Sopenharmony_ci d = deque(range(n)) 1057db96d56Sopenharmony_ci self.it = iter(d) 1067db96d56Sopenharmony_ci self.mutate = d.pop 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ciclass TestDequeReversed(TestTemporarilyImmutable, unittest.TestCase): 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci def setUp(self): 1117db96d56Sopenharmony_ci d = deque(range(n)) 1127db96d56Sopenharmony_ci self.it = reversed(d) 1137db96d56Sopenharmony_ci self.mutate = d.pop 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ciclass TestDictKeys(TestTemporarilyImmutable, unittest.TestCase): 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci def setUp(self): 1187db96d56Sopenharmony_ci d = dict.fromkeys(range(n)) 1197db96d56Sopenharmony_ci self.it = iter(d) 1207db96d56Sopenharmony_ci self.mutate = d.popitem 1217db96d56Sopenharmony_ci 1227db96d56Sopenharmony_ciclass TestDictItems(TestTemporarilyImmutable, unittest.TestCase): 1237db96d56Sopenharmony_ci 1247db96d56Sopenharmony_ci def setUp(self): 1257db96d56Sopenharmony_ci d = dict.fromkeys(range(n)) 1267db96d56Sopenharmony_ci self.it = iter(d.items()) 1277db96d56Sopenharmony_ci self.mutate = d.popitem 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ciclass TestDictValues(TestTemporarilyImmutable, unittest.TestCase): 1307db96d56Sopenharmony_ci 1317db96d56Sopenharmony_ci def setUp(self): 1327db96d56Sopenharmony_ci d = dict.fromkeys(range(n)) 1337db96d56Sopenharmony_ci self.it = iter(d.values()) 1347db96d56Sopenharmony_ci self.mutate = d.popitem 1357db96d56Sopenharmony_ci 1367db96d56Sopenharmony_ciclass TestSet(TestTemporarilyImmutable, unittest.TestCase): 1377db96d56Sopenharmony_ci 1387db96d56Sopenharmony_ci def setUp(self): 1397db96d56Sopenharmony_ci d = set(range(n)) 1407db96d56Sopenharmony_ci self.it = iter(d) 1417db96d56Sopenharmony_ci self.mutate = d.pop 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci## ------- Types that can mutate during iteration ------- 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ciclass TestList(TestInvariantWithoutMutations, unittest.TestCase): 1467db96d56Sopenharmony_ci 1477db96d56Sopenharmony_ci def setUp(self): 1487db96d56Sopenharmony_ci self.it = iter(range(n)) 1497db96d56Sopenharmony_ci 1507db96d56Sopenharmony_ci def test_mutation(self): 1517db96d56Sopenharmony_ci d = list(range(n)) 1527db96d56Sopenharmony_ci it = iter(d) 1537db96d56Sopenharmony_ci next(it) 1547db96d56Sopenharmony_ci next(it) 1557db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n - 2) 1567db96d56Sopenharmony_ci d.append(n) 1577db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n - 1) # grow with append 1587db96d56Sopenharmony_ci d[1:] = [] 1597db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 1607db96d56Sopenharmony_ci self.assertEqual(list(it), []) 1617db96d56Sopenharmony_ci d.extend(range(20)) 1627db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ciclass TestListReversed(TestInvariantWithoutMutations, unittest.TestCase): 1667db96d56Sopenharmony_ci 1677db96d56Sopenharmony_ci def setUp(self): 1687db96d56Sopenharmony_ci self.it = reversed(range(n)) 1697db96d56Sopenharmony_ci 1707db96d56Sopenharmony_ci def test_mutation(self): 1717db96d56Sopenharmony_ci d = list(range(n)) 1727db96d56Sopenharmony_ci it = reversed(d) 1737db96d56Sopenharmony_ci next(it) 1747db96d56Sopenharmony_ci next(it) 1757db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n - 2) 1767db96d56Sopenharmony_ci d.append(n) 1777db96d56Sopenharmony_ci self.assertEqual(length_hint(it), n - 2) # ignore append 1787db96d56Sopenharmony_ci d[1:] = [] 1797db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 1807db96d56Sopenharmony_ci self.assertEqual(list(it), []) # confirm invariant 1817db96d56Sopenharmony_ci d.extend(range(20)) 1827db96d56Sopenharmony_ci self.assertEqual(length_hint(it), 0) 1837db96d56Sopenharmony_ci 1847db96d56Sopenharmony_ci## -- Check to make sure exceptions are not suppressed by __length_hint__() 1857db96d56Sopenharmony_ci 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_ciclass BadLen(object): 1887db96d56Sopenharmony_ci def __iter__(self): 1897db96d56Sopenharmony_ci return iter(range(10)) 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci def __len__(self): 1927db96d56Sopenharmony_ci raise RuntimeError('hello') 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci 1957db96d56Sopenharmony_ciclass BadLengthHint(object): 1967db96d56Sopenharmony_ci def __iter__(self): 1977db96d56Sopenharmony_ci return iter(range(10)) 1987db96d56Sopenharmony_ci 1997db96d56Sopenharmony_ci def __length_hint__(self): 2007db96d56Sopenharmony_ci raise RuntimeError('hello') 2017db96d56Sopenharmony_ci 2027db96d56Sopenharmony_ci 2037db96d56Sopenharmony_ciclass NoneLengthHint(object): 2047db96d56Sopenharmony_ci def __iter__(self): 2057db96d56Sopenharmony_ci return iter(range(10)) 2067db96d56Sopenharmony_ci 2077db96d56Sopenharmony_ci def __length_hint__(self): 2087db96d56Sopenharmony_ci return NotImplemented 2097db96d56Sopenharmony_ci 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ciclass TestLengthHintExceptions(unittest.TestCase): 2127db96d56Sopenharmony_ci 2137db96d56Sopenharmony_ci def test_issue1242657(self): 2147db96d56Sopenharmony_ci self.assertRaises(RuntimeError, list, BadLen()) 2157db96d56Sopenharmony_ci self.assertRaises(RuntimeError, list, BadLengthHint()) 2167db96d56Sopenharmony_ci self.assertRaises(RuntimeError, [].extend, BadLen()) 2177db96d56Sopenharmony_ci self.assertRaises(RuntimeError, [].extend, BadLengthHint()) 2187db96d56Sopenharmony_ci b = bytearray(range(10)) 2197db96d56Sopenharmony_ci self.assertRaises(RuntimeError, b.extend, BadLen()) 2207db96d56Sopenharmony_ci self.assertRaises(RuntimeError, b.extend, BadLengthHint()) 2217db96d56Sopenharmony_ci 2227db96d56Sopenharmony_ci def test_invalid_hint(self): 2237db96d56Sopenharmony_ci # Make sure an invalid result doesn't muck-up the works 2247db96d56Sopenharmony_ci self.assertEqual(list(NoneLengthHint()), list(range(10))) 2257db96d56Sopenharmony_ci 2267db96d56Sopenharmony_ci 2277db96d56Sopenharmony_ciif __name__ == "__main__": 2287db96d56Sopenharmony_ci unittest.main() 229