typing module
typing exists so type-annotated code can import it without
ModuleNotFoundError. No runtime type checking happens. The forms are
inert marker objects and none of them can be subscripted: Optional[str],
List[int] and Callable[[int], str] raise TypeError: 'typing._SpecialForm' object is not subscriptable, and
Union[int, str] raises the 'type' object
wording the builtins use for list[int]. 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. - The blocker is that Monty has no generic types:
list[int]anddict[str, int]raiseTypeError: 'type' object is not subscriptable, andint | NoneraisesTypeError: unsupported operand type(s) for |: 'type' and 'NoneType', so evaluated annotations would fail on the most common forms. Runtimetypes.GenericAliasand|unions are the prerequisite for matching PEP 649. - 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.