Skip to content

The Python Subset

Monty is not a Python implementation aiming for completeness. It implements enough Python for a model to express what it wants to do, and deliberately stops there. Everything it does implement is meant to behave exactly like CPython 3.14; everywhere it does not, the divergence is written down.

This page gives you the shape of the subset. The exhaustive, per-feature list lives in limitations/ in the repository, and that directory — not this page — is the source of truth.

Language features

Supported:

  • def, async def, nested functions, closures, lambda
  • Decorators on functions and classes
  • Simple classes: instance methods, __init__, __repr__/__str__, __eq__/__hash__, __iter__/__next__, __contains__, class variables
  • @dataclass, including the eq= and frozen= options, and host dataclass instances passed in and out
  • List, dict and set comprehensions
  • try / except / else / finally, raise ... from ...
  • for, while, if / elif / else, break, continue, pass, assert, global, nonlocal, return
  • with statements, for files and for classes implementing __enter__ / __exit__
  • f-strings, including =, !r / !s / !a and format specs
  • async / await, and asyncio.run / asyncio.gather
  • import x, import x.y, from x import y, z as w
  • Starred unpacking everywhere CPython allows it

Rejected at parse time, with NotImplementedError before any code runs:

  • Class inheritance and metaclasses (class Foo(Bar):)
  • Decorators on methods — so no @classmethod, @staticmethod, @property
  • yield / yield from — there are no generator functions. Generator expressions parse, but currently materialise to a list
  • match statements
  • del, both del x and del d[k]
  • try* / except* exception groups
  • PEP 695 type aliases
  • async with, async for and async comprehensions
  • Wildcard imports (from m import *)
  • Complex literals (1j) and t-strings

Missing in other ways:

  • User-defined exception classes. The built-in exception types are a fixed set, and without inheritance you cannot add to it.
  • Function attributes. fn.__name__, fn.__doc__ and friends raise AttributeError, and new attributes cannot be set — so functools.wraps-style metadata copying and registries keyed on fn.__name__ have no equivalent.
  • eval, exec, compile, globals, locals, __import__ and super — all raise NameError.
  • Third-party packages. There is no sys.path and no site-packages.

Standard library

Thirteen modules are importable. Anything else raises ModuleNotFoundError.

ModuleDivergences
asyncioasyncio.md
collectionscollections.md
dataclassesdataclasses.md
datetimedatetime.md
itertoolsitertools.md
jsonjson.md
mathmath.md
osos.md
pathlibpathlib.md
rere.md
syssys.md
typingtyping.md
unicodedataunicodedata.md

Each covers only part of its CPython surface — often a small part. The absent names are missing from the module namespace rather than stubbed, so they fail type checking as well as raising AttributeError at runtime.

Notably absent: functools, enum, contextlib, random, time, io, copy, string, struct, operator, inspect, logging, traceback, base64, hashlib, uuid, urllib. Some of those are absent by design — socket, subprocess, multiprocessing, threading and ctypes would breach the sandbox — and others are simply not implemented yet.

The authoritative list is limitations/modules.md.

Things that work but not quite like CPython

A few divergences are worth knowing up front because they change how code behaves rather than whether it runs. Each links to the limitations/ file that owns it, which is where the full account lives:

  • assert failures get pytest-style messages. assert 2 == 5 raises AssertionError: assert 2 == 5, not CPython’s empty AssertionError. Turn it off with assert_message_annotations=False on checkout() (assert.md).
  • enumerate, zip, map, filter and reversed are eager, not lazy. So map(f, itertools.count()) runs until a resource limit trips (builtins.md).
  • re is backed by Rust’s fancy-regex, not CPython’s engine: no bytes patterns, no VERBOSE flag, and some error messages differ (re.md).
  • There is no event loop inside the sandbox. async / await work, and asyncio exposes exactly two functions: run and gather, the latter running host calls concurrently. create_task, sleep and everything else do not exist (asyncio.md).
  • str.format() and %-formatting are not implemented. Use f-strings (format.md).
  • Only UTF-8, ASCII, UTF-16 and UTF-32 codecs exist. latin-1 and friends raise LookupError (encoding.md).

How to go deeper

For a specific feature, open the limitations/ file named after the builtin, module or construct. If you hit something that is neither in the subset nor in limitations/, open an issue.