Python language / parser
Monty parses Python source with Ruff’s parser but rejects several constructs
at parse time. Anything listed below raises NotImplementedError (prefixed
with “The monty syntax parser does not yet support ”) at compile time, before
any code runs.
classdefinitions — simple classes are supported (instance methods,__init__/__repr__/__str__, class variables of arbitrary expressions). Rejected at parse time: base classes / metaclasses (class Foo(Bar):) and class-body statements other thandef, a simplename [: T] = <expr>assignment,pass, or a docstring. There is no inheritance and no general dunder protocol. See classes.md.- Decorators (
@deco) — supported on classes and on top-level or nesteddef/async def, taking any callable in scope, evaluated in the enclosing scope and applied bottom-up. Rejected at parse time on methods, so@classmethod,@staticmethod,@propertyand any decorator on adefinside a class body are unavailable. See classes.md. async withstatements — not yet supported.yield/yield fromexpressions — no generator functions. Generator expressions ((x for x in ...)) parse but currently materialize to alistrather than a lazy iterator, a known temporary divergence; seeiter__generator_expr_type.py.matchstatements — structural pattern matching is not supported.delstatements — neitherdel xnordel d[k]parse.try*/except*exception groups — PEP 654 syntax rejected.typealiases (PEP 695type Foo = int).async forloops and async comprehensions.- Wildcard imports (
from m import *) — raisesNotImplementedError: "Wildcard imports (`from ... import *`) are not supported".
- Complex number literals (
1j,2+3j) —NotImplementedError: The monty syntax parser does not yet support complex constants. - Template strings (t-strings) — PEP 750.
Anything Monty can iterate may follow a *, matching CPython: [*xs],
(*xs,), {*xs}, f(*xs), a, b = xs and a, *b = xs all accept whatever
list(xs) accepts.
One message divergence: passing a non-iterable to a call, f(*1), reports
TypeError: Value after * must be an iterable, not int, the same wording as a
list literal. CPython instead names the callable by its module-qualified
__qualname__: __main__.f() argument after * must be an iterable, not int,
and correspondingly __main__.C.m(), __main__.<lambda>() or
__main__.outer.<locals>.inner(). Monty has neither function __qualname__
nor module-qualified names (see the class-name note in collections.md), so
it reports the generic form. Every other unpacking form matches CPython
exactly.
- AST nesting is capped at 200 levels (30 in debug builds); exceeding it raises
SyntaxError: Source is too deeply nested. - The budget is shared across every nesting-producing construct (parens, calls, subscripts, attribute chains, operators,
comprehensions, control-flow blocks,
with, etc.), including the synthetic nesting from a flat multi-itemwith; see with.md. - The message differs from CPython, which uses construct-specific wording (
too many nested parentheses,too many statically nested blocks, …). - Class-body annotations count against the budget even though they are stringized rather than evaluated (see typing.md), as do class-variable values and method parameter defaults; all three are walked before being parsed. CPython imposes no comparable limit on a stringized annotation.
- Only the bundled stdlib modules listed in modules.md can be
imported. Importing anything else raises
ModuleNotFoundError. - Relative imports (
from . import x) raiseImportError: "attempted relative import with no known parent package"; there is no package system. __import__is not defined.
from __future__ import ... is a compiler directive, not a real import: it
binds nothing and is accepted as a no-op. Of CPython’s ten features, eight
became mandatory in Python 3.7 or earlier and so are inert there too, and
annotations is a no-op here because Monty already stringizes annotations
(see typing.md). Divergences:
barry_as_FLUFL(PEP 401) raisesNotImplementedError: "The monty syntax parser does not yet support the 'barry_as_FLUFL' future feature". CPython accepts it, making<>the inequality operator and!=aSyntaxError; Monty parses neither differently, so the import is rejected rather than silently ignored.- Aliasing is rejected.
from __future__ import annotations as annraisesNotImplementedError: "The monty syntax parser does not yet support aliasing a `__future__` feature". CPython bindsannto a__future__._Featureobject; a no-op would bind nothing and surface as aNameErrorfar from the import, so it is rejected at the import instead. - Position is not enforced. CPython requires
__future__imports to precede all other statements (SyntaxError: "from __future__ imports must occur at the beginning of the file"); Monty accepts them anywhere. import __future__(as opposed tofrom __future__ import ...) raisesModuleNotFoundError; there is no__future__module object.
Monty has no module object and no globals() dict, but it exposes a fixed set
of module-level dunders so common idioms (e.g. if __name__ == '__main__':)
work. They are resolved on read; there is no real namespace entry behind them.
| Name | Monty value | CPython (script run) |
|---|---|---|
__name__ | '__main__' | '__main__' |
__debug__ | True | True |
__doc__ | None | None or docstring str |
__spec__ | None | None |
__package__ | None | None |
__annotations__ | empty dict | NameError (no annotations) |
In Monty __doc__ is always None, since module docstrings are never
extracted, and __annotations__ is always an empty dict because
module-level annotations are not stored (see typing.md); CPython 3.14
instead raises NameError when a module has no annotations (PEP 649).
These names are read-only: assigning one at module or global scope (including
via global __name__ inside a function, and augmented assignment like
__name__ += ...) is rejected at compile time with
NotImplementedError: cannot reassign read-only module attribute '<name>'.
CPython instead allows rebinding most of them (it is how you set a module
docstring), and rejects only __debug__, with a SyntaxError.
Binding one of these names as a function local is allowed (it is an
ordinary local in a separate namespace), matching CPython, except __debug__,
which CPython rejects everywhere with SyntaxError but Monty permits as a
local.
Other module dunders CPython defines (__loader__, __file__, __builtins__,
__cached__, __dict__) are not exposed; reading them falls through to the host
name lookup and ultimately raises NameError if unresolved. __loader__ is
omitted because CPython always binds it to a loader object (never None), so
exposing None would diverge on type, and a real loader is neither available
nor safe to surface in the sandbox. __file__ is omitted so no host path can
leak into the sandbox.
A function exposes no attributes: __name__, __doc__, __qualname__ and
__module__ all raise AttributeError: 'function' object has no attribute '<name>', and new ones cannot be set —
fn.tag = True raises
AttributeError: 'function' object has no attribute 'tag' and no __dict__ for setting new attributes. CPython supports
all of these.
This bounds what a decorator can do: it can call, wrap, store or replace the
function it receives, but cannot ask the function about itself, so
functools.wraps-style metadata copying, registries keyed by fn.__name__, and
attribute tagging for later discovery all have no equivalent.
<, <=, >, >= on operands with no defined ordering raise
TypeError: '<' not supported between instances of '{a}' and '{b}', matching
CPython (int vs str, None vs None, user-class instances without comparison
dunders, etc.). Lists and tuples order lexicographically as in CPython. A NaN
operand is unordered rather than incomparable, so float('nan') < 1 (and
every operator/direction, including two NaNs) returns False without raising,
also matching CPython, and likewise inside sorted/min/max.
One message divergence: when a list or tuple compares unequal only because
an inner element pair is unorderable (e.g. (1, 2) < (1, 'a')), Monty names
the outer container types ('tuple' and 'tuple') where CPython names the inner
element pair ('int' and 'str'). Both raise TypeError; only the message text
differs.
One value divergence: whenever CPython compares elements inside a container it
shortcuts equality by object identity (x is x ⇒ equal) before falling back
to ==. Monty has no object identity for immediate floats, so it asks ==, and
a NaN is never equal to itself. Every container operation built on element
comparison therefore differs when the same NaN object appears on both sides:
with x = float('nan') | CPython | Monty |
|---|---|---|
(x,) == (x,), [x] == [x] | True | False |
x in [x] | True | False |
[x].count(x) | 1 | 0 |
[x].index(x) | 0 | ValueError |
[1, x] < [1, x, 3] | True | False |
NaN is the only practical way to reach this, being the one built-in value
unequal to itself. Distinct NaN objects agree on both
([1, float('nan')] < [1, float('nan'), 3] is False either way), as does a
direct x == x (False on both). Named tuples inherit all of this from tuple.
- Functions (
def,async def), nested functions, closures, and decorators on them, but not on methods (see above). - List / dict / set comprehensions; generator comprehensions degrade to lists (see above).
try/except/else/finally,raise ... from ....for/while/if/elif/else,break,continue,pass,assert,global,nonlocal,return.import x,import x.y,from x import y, z as w.- f-strings including
=debug specifier,!r/!s/!aconversions, and format specs.