typing module
typing exists so type-annotated code can import it without
ModuleNotFoundError. No runtime type checking happens. Apart from
Union and Optional (see Unions) the forms are inert marker
objects that cannot be subscripted: List[int] and Callable[[int], str]
raise TypeError: 'typing._SpecialForm' object is not subscriptable.
Annotations are unaffected, being stringized rather than evaluated (see below).
Any, Optional, Union, List, Dict, Tuple, Set, FrozenSet,
Callable, Type, Sequence, Mapping, Iterable, Iterator,
Generator, ClassVar, Final, Literal, TypeVar, Generic,
Protocol, Annotated, Self, Never, NoReturn, TYPE_CHECKING.
TYPE_CHECKING is False, as in CPython at runtime.
get_type_hints,get_args,get_origin,cast,assert_type,assert_never,overload,final,runtime_checkable,NewType,NamedTuple,TypedDict,dataclass_transform,ParamSpec,Concatenate,Unpack,TypeAlias,TypeAliasType,LiteralString.- Annotation introspection on functions and modules:
__annotations__is not populated there. Class__annotations__is populated; see below.
A class body’s annotations are recorded, in order, on the class’s
__annotations__ dict, but in stringized form, unconditionally. The values
are the annotation expression rendered back to source, never evaluated. As in
CPython’s PEP 563 stringizer the expression is unparsed rather than sliced out
of the file, so original spacing, line breaks and quote style are normalized
away (x: dict[str,int] gives 'dict[str, int]'):
class C:
x: int
y: list[int]
C.__annotations__ # {'x': 'int', 'y': 'list[int]'} -- strings
This is a known temporary divergence; see class__annotations.py.
- Divergence from CPython 3.14’s default (PEP 649), where these are the
evaluated objects (
C.__annotations__['x'] is int). CPython only agrees with Monty when the calling code usesfrom __future__ import annotations(PEP 563), which Monty’s behaviour is otherwise equivalent to, except that Monty stringizes whether or not that import is present. - Treat the values as provisional. Code reading
__annotations__sees strings today and would see type objects after a PEP 649 migration; the keys and their order are stable either way. - Only simple
name: Ttargets are recorded, as in CPython. A bareobj.attr: Tcontributes nothing to__annotations__on either, but CPython still evaluates the target expression:undefined.attr: intraisesNameErrorthere and is silently dropped by Monty. With a value (obj.attr: T = v) Monty raisesNotImplementedError. - Binding
__annotations__explicitly in a class body that also has annotated names raisesNotImplementedError. CPython instead stores the collected annotations into whatever the name holds, merging into an explicitdict, or raisingTypeErrorif it holds something else. A class body that binds the name but annotates nothing is accepted, and its binding stands. from __future__ import annotationsis accepted as a no-op, since it describes what Monty already does. See language.md for the other features.- Consequences:
get_type_hints()(which would evaluate the strings) is still not implemented, and code that reads__annotations__expecting type objects sees strings. CPython 3.14’s@dataclassreads evaluated objects (annotationlib.Format.FORWARDREF), but keeps a string path forClassVar/InitVarso PEP 563 code still works, which is what makes stringized annotations enough to build on.
If you need real type validation, do it on the host side around the sandbox boundary.
Subscripting a builtin type builds a types.GenericAlias, but only for
list, tuple, dict, set, frozenset, type, collections.deque,
collections.defaultdict, collections.Counter, functools.partial,
re.Pattern and re.Match. Every other type raises
TypeError: type 'int' is not subscriptable, including ones CPython
parameterizes:
enumerate(a builtin function in Monty, not a type).collections.namedtupleclasses, which in CPython inherittuple.__class_getitem__;Point[int]raises.- User classes:
__class_getitem__is not looked up, soFoo[int]raises whether or not the class defines it.
Divergences in the aliases themselves:
- No
typesmodule.type(list[int])reprs as<class 'types.GenericAlias'>, butimport typesraisesModuleNotFoundError, soisinstance(x, types.GenericAlias)cannot be written; comparetype(x) is type(list[int])instead. - Not iterable. CPython iterates an alias to yield its starred form
(
*tuple[int, ...]); Monty raisesTypeError: 'types.GenericAlias' object is not iterable. - Argument reprs use Monty’s type names. A user class prints its bare name
(
list[Foo]where CPython printslist[__main__.Foo], see classes.md), andcollections.Counter[str]prints asCounter[str]. - An unhashable argument names the alias.
hash(list[[1]])raisesTypeError: unhashable type: 'types.GenericAlias'where CPython names the argument ('list'), as with a tuple holding a list. - A namedtuple subscript is one argument.
list[Point(int, str)]keeps the namedtuple as its single argument, where CPython’sPyTuple_Checkunpacks it intolist[int, str]. - A cycle through the arguments prints as
.... CPython’s alias repr has no recursion guard and raisesRecursionErroronl = []; l.append(list[l]); repr(l); Monty prints[list[[...]]]. - No
__class__.list[int].__class__raisesAttributeError, as it does for every builtin value (see builtins.md); usetype(list[int]). - No
__orig_class__on call results. CPython sets it on the result of calling an alias when the object accepts attributes, sofunctools.partial[int](f).__orig_class__isfunctools.partial[int]; Monty’s partial objects take no attributes, so the lookup raisesAttributeError.partialis the only subscriptable type whose CPython instances accept it.
int | None, typing.Union[int, str] and typing.Optional[int] build a
typing.Union, as in CPython 3.14. Divergences:
- Member reprs use Monty’s type names, as in a generic alias:
Foo | Nonewhere CPython prints__main__.Foo | None. - An unhashable member names the union.
hash(int | list[[1]])raisesTypeError: unhashable type: 'typing.Union'where CPython names the member. - No attributes beyond
__args__,__origin__and__parameters__.__class__,__or__and the other dunders CPython exposes raiseAttributeError. - String members stay strings.
Optional['Foo']is'Foo' | Nonewhere CPython wraps the string asForwardRef('Foo'); there is noForwardRef. - Special forms are accepted as members.
Optional[typing.Final]buildstyping.Final | Nonewhere CPython raisesTypeError: Plain typing.Final is not valid as type argument. - Neither aliases nor unions cross the host boundary. One built in the
sandbox reaches the host as its repr string. Passed in from the host, a
list[int]degrades to an external function (it is callable, so it is treated like any unmodeled class) and anint | Noneis rejected withMontyConversionError; neither has aMontyObjectform. Their type objects round-trip:types.GenericAliasby identity, and Monty’styping.Unionas the host’stypes.UnionType, which istyping.Unionitself only from Python 3.14.