itertools module
Monty implements a small subset of itertools. The implemented callables match
CPython 3.14 for arguments, values, repr() and error messages, apart from the
notes below.
count(start=0, step=1), repeat(object, times=?), pairwise(iterable),
compress(data, selectors), islice(iterable, [start,] stop[, step]),
chain(*iterables), cycle(iterable), takewhile(predicate, iterable),
dropwhile(predicate, iterable), filterfalse(predicate, iterable),
starmap(function, iterable).
Everything else: accumulate, batched, combinations,
combinations_with_replacement, groupby, permutations, product, tee,
zip_longest.
chain.from_iterable is also absent, even though chain itself is
implemented: it is a classmethod reached through an attribute on the chain
builtin, and Monty’s module functions expose no attributes
(itertools.chain.from_iterable raises
AttributeError: 'builtin_function_or_method' object has no attribute 'from_iterable'). Use chain(*iterables)
instead.
These names are absent from the module namespace rather than stubbed, so they
are rejected at type-check time (Module 'itertools' has no member 'chain') and
raise AttributeError at runtime.
repeat.__length_hint__()raisesAttributeError. CPython exposes the number of remaining yields through it (repeat(9, 3).__length_hint__() == 3). Monty uses the remaining count internally to size the target oflist()/tuple(), but does not expose it as a Python-visible attribute.countandrepeatobjects are unhashable.hash(itertools.count())raisesTypeError: unhashable type: 'itertools.count', where CPython falls back to identity hashing. This applies to Monty’s iterators generally, not just these two.countaccepts onlyint,floatandbool. CPython accepts anything satisfyingPyNumber_Check(e.g.Decimal,Fraction, complex). Monty has no other numeric types, so the sameTypeError: a number is requiredcovers them all.- Nested-cycle
repr()unwinds one level earlier. For a container that reaches back to therepeatholding it, Monty printsrepeat([...])where CPython printsrepeat([repeat([...])]). This is Monty’s general cycle detection inrepr(), not specific toitertools. - A callable that suspends is rejected, not paused.
takewhile,dropwhile,filterfalseandstarmapapply their callable through the synchronousevaluate_functionpath, which runs a frame to completion and cannot yield to the host. A callable that reaches an external function, anosoperation, or a host method call therefore raisesNotImplementedError: takewhile(): external function 'f' is not yet supported in this contextwhere CPython would simply call it. This is the same restriction that applies to__init__,__next__and__repr__(see classes.md); ordinary sandbox-defined functions and lambdas are unaffected. - Crossing the host boundary loses the repr. A
count/repeatobject returned to the host arrives as<itertools.count object>/<itertools.repeat object>rather than its in-sandboxrepr()(count(0),repeat(7, 3)). Monty represents all iterators this way rather than recursing into what they hold.
map(), filter() and enumerate() are eager in Monty: each drains its
source into a list and returns a concrete result, rather than the lazy iterator
CPython returns. Applied to an infinite itertools iterator they therefore
never return, where CPython yields lazily:
import itertools
map(str, itertools.count()) # CPython: lazy. Monty: runs until a limit trips.
filter(bool, itertools.repeat(1)) # likewise
enumerate(itertools.count()) # likewise
zip() stops at the shortest input, so zip(itertools.count(), 'ab') behaves
as in CPython, as does slicing an infinite iterator by hand via next(). This
is a pre-existing property of those builtins rather than something itertools
introduces, but count()/repeat() are the first easy way for sandboxed code
to reach it.
count() and repeat(x) are infinite, so consuming one without a bound
(list(itertools.count())) only terminates if the host has configured a memory
or duration limit, and then raises MemoryError rather than exhausting. Under
ResourceLimits::default(), which sets neither (only a recursion depth), it
runs until the host itself runs out of memory. This is the same exposure as a
while True: loop, not something specific to itertools.
The adaptors that discard items without yielding — dropwhile and
filterfalse before their first accepted item, compress past a falsy run,
islice skipping to start, chain crossing an exhausted source — poll
max_duration themselves while looping, so a discarding pass over an infinite
source raises TimeoutError instead of spinning. The poll is amortized (once
per 64 items), so the limit can be overshot by up to that much work. CPython
has no duration limit at all and would loop forever.
cycle(iterable) must buffer every item it has seen so far in order to replay
them, and that buffer is charged against max_memory as it grows, so cycling
over a very long source raises MemoryError at the limit rather than at the
point the source is exhausted. CPython buffers the same items with no such
ceiling.
Nesting the source-driving adaptors (pairwise, compress, islice, chain,
cycle) is bounded by max_recursion_depth: each adaptor charges one recursion
level while delegating next() to its wrapped iterator, so a nest deeper than
the limit raises RecursionError when consumed. CPython imposes no comparable
per-adaptor bound; deep nesting there is limited only by the C stack.