Built-in functions
Monty implements a subset of CPython’s builtins. Referencing any name not
listed here raises NameError at runtime; there is no fallback to a host
Python.
abs, all, any, bin, chr, divmod, enumerate, filter,
getattr, hasattr, hash, hex, id, isinstance, iter, len, map,
max, min, next, oct, open, ord, pow, print, repr,
reversed, round, setattr, sorted, sum, type, zip.
bool, bytes, dict, float, frozenset, int, list, range,
set, slice, str, tuple. Exception classes (ValueError,
TypeError, etc.) are also names in the builtin namespace.
These raise NameError:
- Code execution:
eval,exec,compile,__import__. Deliberate: sandboxed code must not be able to compile new code at runtime. - Namespace introspection:
globals,locals,vars,dir. - Interactive:
input,breakpoint,help. - Decorators / descriptors:
classmethod,staticmethod,property,super. (@propertyon functions is not recognized; use a method.) - Construction / coercion:
bytearray,complex,memoryview,object,format,ascii. - Other:
callable,delattr,issubclass,aiter,anext.
super() is the biggest practical omission: with no class inheritance either
(see classes.md), there is no inheritance mechanism at all.
reprof a dict being mutated by its own elements — Monty iterates the live entries like CPython, but deletion compacts Monty’s dense entry storage where CPython leaves a tombstone in place: a key deleted from inside a user__repr__running during that dict’s repr shifts later entries down, so the entry after the deleted one can be skipped from the output where CPython would still print it. Insertions during repr match CPython (appended and printed), as do list (live length, mid-repr pops truncate / appends extend),set,collections.dequeandcollections.Counter(all snapshot, like CPython).- dict/set lookups under a mutating
__eq__— like CPython, a lookup (in,d[k],set.remove, …) whose user__eq__mutates the container keeps probing rather than raising, and a mutation that only adds colliding keys never makes it repeat a comparison. Monty re-reads the colliding candidates after each comparison where CPython walks the live probe chain, so an__eq__that moves the entry being compared restarts the probe and re-compares the other candidates from scratch — extra__eq__calls CPython would not make (CPython restarts and re-compares too, but only after a resize or when that entry’s own slot changed). An__eq__that adds a colliding key on every comparison never finishes in either engine; undermax_durationMonty raisesTimeoutError. No mutation pattern can panic or corrupt either engine. enumerate,zip,map,filterandreversedare eager, not lazy — each drains its source and returns alist, sotype(enumerate(x)).__name__is'list'rather than'enumerate'. Observable several ways: a side-effecting callable runs for every item at the call itself rather than as the result is consumed; the whole result is held in memory at once, so an infinite iterator (e.g.map(f, itertools.count())) never returns and runs until a resource limit trips; the result can be indexed and re-iterated, which CPython forbids; and mutating the source from inside the loop body is never observed, so containers that detect mutation during iteration (dict,set,collections.deque) will not raise when looped over via one of these.zipand multi-iterablemapstop at the shortest input, so pairing an infinite iterable with a finite or empty one stays bounded. A plainfor x in containeris lazy and does detect mutation. See itertools.md.- Arity-error wording for some str/bytes methods — a handful of
keyword-accepting methods (e.g.
str.split,str.rsplitand thebytesequivalents) report too-many-arguments assplit expected at most 2 arguments, got 3, where CPython 3.14’s Argument Clinic pre-counts positionals plus kwargs and sayssplit() takes at most 2 arguments (3 given). Methods audited against CPython (encode,decode,expandtabs,splitlines,replace, …) already match; the remainder need a per-functionat_most_totalaudit. getattr(obj, name)— if the resolved attribute would be an async coroutine, external function, or OS call, raisesTypeError: "getattr(): attribute is not a simple value"rather than returning a bound method object. Use direct attribute access (obj.name(...)) for these.int(x, base=10)— string/bytes parsing accepts ASCII digits only; CPython also accepts non-ASCII Unicode decimal digits (int('١٢')== 12), which Monty rejects withinvalid literal for int() with base 10.bytes(source)— an iterable of ints is not supported: CPython’sbytes([65, 66])==b'AB', Monty raisesTypeError: cannot convert 'list' object to bytes. The int / str-with-encoding / bytes source forms all work. A count abovei64(bytes(2**70)) raises the same shape ofTypeError(cannot convert 'int' object to bytes), not CPython’sOverflowError: cannot fit 'int' into an index-sized integer.isinstance(obj, T)—Tmust be a built-in type (int,str,list, …), a built-in exception class, a sandbox-defined class (see classes.md), or a tuple of those. Passing a host-supplied dataclass / namedtuple as the second argument raisesTypeError.iter()— see iter.md for iterator anditer(callable, sentinel)divergences.pow(base, exp, mod)— the three-argument form requires all integers and rejects negative exponents withValueErrorinstead of computing a modular inverse. Non-modular exponents whose result cannot be materialized raiseOverflowError(see resource_limits.md).sorted(iterable, *, key=None, reverse=False)—keyandreversemust be passed by keyword; positional forms raiseTypeError.round(n, ndigits)—ndigitsvalues outside the i64 range are clamped by sign. For floats this matches CPython (which clamps toPy_ssize_t); for an intnwith a hugely negativendigits, CPython tries to materialise10**-ndigitsand dies withMemoryErrorwhere Monty returns0immediately.print— writes via the host print callback.file=,flush=are not honoured;sep=andend=are.- Identity of host-supplied callables — host functions passed in as inputs
(
MontyObject::Function) lose their host object identity at the sandbox boundary. Live external functions are identified by lookup name, so distinct host callables with the same name shareis, equality,id(), andhash()results. Once the last sandbox reference is dropped, a later conversion of that name may create a new function object. - Type objects across the host boundary — a
typeobject (a class, not an instance) round-trips in both directions.- Sandbox → host (external/OS-call argument, or a
.run()return value): the type is reconstructed as the corresponding host class. Genuine builtins (int,str,type,bytes,list,dict,property, …) resolve to the real builtin; Monty’s modeled stdlib types map to their host stdlib class:datetime/date/timedelta/timezone→datetime.*,re.Pattern/re.Match→re.*, the binary/text file types →io.*. Thepathlib.Pathclass maps topathlib.PurePosixPath, consistent with how Path instances round-trip, and instantiable on every host OS. A type with no faithful host class (e.g. an internal function or cell type) cannot be reconstructed and surfaces as anAttributeErrorfrom the host call. - Host → sandbox (input, or an external-call return value): the same recognized
builtins and modeled stdlib types are preserved as type objects, so
isinstance(x, the_type)works inside the sandbox. Recognition is by type-object identity, not class name/module, so a class that forges__name__/__module__to impersonate a builtin is not treated as one. Everypathlibpath class collapses toPurePosixPath(it re-emerges asPurePosixPath). A host class Monty does not model (e.g. a user-defined class) is not preserved as a type; it degrades to a callable, appearing inside the sandbox as afunctionrather than atype.
- Sandbox → host (external/OS-call argument, or a