summaryrefslogtreecommitdiff
path: root/lib/python2.7/test/test_iterlen.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/test/test_iterlen.py')
-rw-r--r--lib/python2.7/test/test_iterlen.py257
1 files changed, 257 insertions, 0 deletions
diff --git a/lib/python2.7/test/test_iterlen.py b/lib/python2.7/test/test_iterlen.py
new file mode 100644
index 0000000..88f43c7
--- /dev/null
+++ b/lib/python2.7/test/test_iterlen.py
@@ -0,0 +1,257 @@
+""" Test Iterator Length Transparency
+
+Some functions or methods which accept general iterable arguments have
+optional, more efficient code paths if they know how many items to expect.
+For instance, map(func, iterable), will pre-allocate the exact amount of
+space required whenever the iterable can report its length.
+
+The desired invariant is: len(it)==len(list(it)).
+
+A complication is that an iterable and iterator can be the same object. To
+maintain the invariant, an iterator needs to dynamically update its length.
+For instance, an iterable such as xrange(10) always reports its length as ten,
+but it=iter(xrange(10)) starts at ten, and then goes to nine after it.next().
+Having this capability means that map() can ignore the distinction between
+map(func, iterable) and map(func, iter(iterable)).
+
+When the iterable is immutable, the implementation can straight-forwardly
+report the original length minus the cumulative number of calls to next().
+This is the case for tuples, xrange objects, and itertools.repeat().
+
+Some containers become temporarily immutable during iteration. This includes
+dicts, sets, and collections.deque. Their implementation is equally simple
+though they need to permanently set their length to zero whenever there is
+an attempt to iterate after a length mutation.
+
+The situation slightly more involved whenever an object allows length mutation
+during iteration. Lists and sequence iterators are dynamically updatable.
+So, if a list is extended during iteration, the iterator will continue through
+the new items. If it shrinks to a point before the most recent iteration,
+then no further items are available and the length is reported at zero.
+
+Reversed objects can also be wrapped around mutable objects; however, any
+appends after the current position are ignored. Any other approach leads
+to confusion and possibly returning the same item more than once.
+
+The iterators not listed above, such as enumerate and the other itertools,
+are not length transparent because they have no way to distinguish between
+iterables that report static length and iterators whose length changes with
+each call (i.e. the difference between enumerate('abc') and
+enumerate(iter('abc')).
+
+"""
+
+import unittest
+from test import test_support
+from itertools import repeat
+from collections import deque
+from __builtin__ import len as _len
+
+n = 10
+
+def len(obj):
+ try:
+ return _len(obj)
+ except TypeError:
+ try:
+ # note: this is an internal undocumented API,
+ # don't rely on it in your own programs
+ return obj.__length_hint__()
+ except AttributeError:
+ raise TypeError
+
+class TestInvariantWithoutMutations(unittest.TestCase):
+
+ def test_invariant(self):
+ it = self.it
+ for i in reversed(xrange(1, n+1)):
+ self.assertEqual(len(it), i)
+ it.next()
+ self.assertEqual(len(it), 0)
+ self.assertRaises(StopIteration, it.next)
+ self.assertEqual(len(it), 0)
+
+class TestTemporarilyImmutable(TestInvariantWithoutMutations):
+
+ def test_immutable_during_iteration(self):
+ # objects such as deques, sets, and dictionaries enforce
+ # length immutability during iteration
+
+ it = self.it
+ self.assertEqual(len(it), n)
+ it.next()
+ self.assertEqual(len(it), n-1)
+ self.mutate()
+ self.assertRaises(RuntimeError, it.next)
+ self.assertEqual(len(it), 0)
+
+## ------- Concrete Type Tests -------
+
+class TestRepeat(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = repeat(None, n)
+
+ def test_no_len_for_infinite_repeat(self):
+ # The repeat() object can also be infinite
+ self.assertRaises(TypeError, len, repeat(None))
+
+class TestXrange(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = iter(xrange(n))
+
+class TestXrangeCustomReversed(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = reversed(xrange(n))
+
+class TestTuple(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = iter(tuple(xrange(n)))
+
+## ------- Types that should not be mutated during iteration -------
+
+class TestDeque(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = deque(xrange(n))
+ self.it = iter(d)
+ self.mutate = d.pop
+
+class TestDequeReversed(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = deque(xrange(n))
+ self.it = reversed(d)
+ self.mutate = d.pop
+
+class TestDictKeys(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = dict.fromkeys(xrange(n))
+ self.it = iter(d)
+ self.mutate = d.popitem
+
+class TestDictItems(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = dict.fromkeys(xrange(n))
+ self.it = d.iteritems()
+ self.mutate = d.popitem
+
+class TestDictValues(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = dict.fromkeys(xrange(n))
+ self.it = d.itervalues()
+ self.mutate = d.popitem
+
+class TestSet(TestTemporarilyImmutable):
+
+ def setUp(self):
+ d = set(xrange(n))
+ self.it = iter(d)
+ self.mutate = d.pop
+
+## ------- Types that can mutate during iteration -------
+
+class TestList(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = iter(range(n))
+
+ def test_mutation(self):
+ d = range(n)
+ it = iter(d)
+ it.next()
+ it.next()
+ self.assertEqual(len(it), n-2)
+ d.append(n)
+ self.assertEqual(len(it), n-1) # grow with append
+ d[1:] = []
+ self.assertEqual(len(it), 0)
+ self.assertEqual(list(it), [])
+ d.extend(xrange(20))
+ self.assertEqual(len(it), 0)
+
+class TestListReversed(TestInvariantWithoutMutations):
+
+ def setUp(self):
+ self.it = reversed(range(n))
+
+ def test_mutation(self):
+ d = range(n)
+ it = reversed(d)
+ it.next()
+ it.next()
+ self.assertEqual(len(it), n-2)
+ d.append(n)
+ self.assertEqual(len(it), n-2) # ignore append
+ d[1:] = []
+ self.assertEqual(len(it), 0)
+ self.assertEqual(list(it), []) # confirm invariant
+ d.extend(xrange(20))
+ self.assertEqual(len(it), 0)
+
+## -- Check to make sure exceptions are not suppressed by __length_hint__()
+
+
+class BadLen(object):
+ def __iter__(self): return iter(range(10))
+ def __len__(self):
+ raise RuntimeError('hello')
+
+class BadLengthHint(object):
+ def __iter__(self): return iter(range(10))
+ def __length_hint__(self):
+ raise RuntimeError('hello')
+
+class NoneLengthHint(object):
+ def __iter__(self): return iter(range(10))
+ def __length_hint__(self):
+ return None
+
+class TestLengthHintExceptions(unittest.TestCase):
+
+ def test_issue1242657(self):
+ self.assertRaises(RuntimeError, list, BadLen())
+ self.assertRaises(RuntimeError, list, BadLengthHint())
+ self.assertRaises(RuntimeError, [].extend, BadLen())
+ self.assertRaises(RuntimeError, [].extend, BadLengthHint())
+ self.assertRaises(RuntimeError, zip, BadLen())
+ self.assertRaises(RuntimeError, zip, BadLengthHint())
+ self.assertRaises(RuntimeError, filter, None, BadLen())
+ self.assertRaises(RuntimeError, filter, None, BadLengthHint())
+ self.assertRaises(RuntimeError, map, chr, BadLen())
+ self.assertRaises(RuntimeError, map, chr, BadLengthHint())
+ b = bytearray(range(10))
+ self.assertRaises(RuntimeError, b.extend, BadLen())
+ self.assertRaises(RuntimeError, b.extend, BadLengthHint())
+
+ def test_invalid_hint(self):
+ # Make sure an invalid result doesn't muck-up the works
+ self.assertEqual(list(NoneLengthHint()), list(range(10)))
+
+
+def test_main():
+ unittests = [
+ TestRepeat,
+ TestXrange,
+ TestXrangeCustomReversed,
+ TestTuple,
+ TestDeque,
+ TestDequeReversed,
+ TestDictKeys,
+ TestDictItems,
+ TestDictValues,
+ TestSet,
+ TestList,
+ TestListReversed,
+ TestLengthHintExceptions,
+ ]
+ test_support.run_unittest(*unittests)
+
+if __name__ == "__main__":
+ test_main()