limitations/language.md
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.
class definitions — 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 than def, a simple name [: T] = <expr>
assignment, pass, or a docstring. There is no inheritance and no general
dunder protocol. See classes.md.@deco) — supported on classes and on top-level or nested
def/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, @property and any decorator on a def
inside a class body are unavailable. See classes.md.async with statements — not yet supportedyield / yield from expressions — no generator functions. Generator
expressions ((x for x in ...)) parse but currently materialize to a
list rather than a lazy iterator (this is a known temporary divergence;
see iter__generator_expr_type.py).match statements — structural pattern matching is not supported.del statements — neither del x nor del d[k] parse.try* / except* exception groups — PEP 654 syntax rejected.type aliases (PEP 695 type Foo = int).async for loops and async comprehensions.from m import *) — raises ImportError: "Wildcard imports (\from ... import *`) are not supported"`.1j, 2+3j) — NotImplementedError: The monty syntax parser does not yet support complex constants.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.
SyntaxError: Source is too deeply nested.with, etc.), including the synthetic nesting from a flat multi-item with — see with.md.too many nested parentheses, too many statically nested blocks, …).ModuleNotFoundError.from . import x) raise ImportError: "attempted relative import with no known parent package" — there is no package
system.__import__ is not defined.__future__ importsfrom __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) raises NotImplementedError: "The monty syntax parser does not yet support the 'barry_as_FLUFL' future feature".
CPython accepts it, making <> the inequality operator and != a
SyntaxError; Monty parses neither differently, so the import is rejected
rather than silently ignored.from __future__ import annotations as ann raises
NotImplementedError: "The monty syntax parser does not yet support aliasing a \future` feature". CPython binds annto afuture._Featureobject; a no-op would bind nothing and surface as aNameError` far from the
import, so it is rejected at the import instead.__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 to from __future__ import ...) raises
ModuleNotFoundError — 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 — 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 is the ceiling on 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.
def, async def), nested functions, closures, and decorators on
them (but not on methods — 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.= debug specifier, !r/!s/!a conversions, and
format specs.