typed dict literals¶
a dict-shaped expression in an annotation position is a TypedDict:
shape identity ignores key order — {"a": int, "b": str} and
{"b": str, "a": int} are the same type
typing_extensions is a runtime requirement of any module that contains a
typed-dict-literal annotation (the generated type uses PEP 728
closed/extra_items features that aren't yet in typing.TypedDict)
closed by default, **: T for extra items¶
dict literal types are closed by default, so extra keys are rejected. a
**: T entry switches that to "extra keys allowed, must match T":
a: {"key": int} # closed — extra keys rejected
b: {"key": int, **: str} # extra keys allowed, must be str
nested shapes¶
nested typed-dict literals work transparently:
type variables¶
a dict literal type is not a generic class of its own, but its fields can name the type variables of the scope it is written in. specializing that scope substitutes them:
a keyword-variadic pack splices its whole field list in with {**Kwargs}
display¶
a dict literal type reads back as its shape rather than as the generated class name, with keys quoted the way the source spells them and fields ordered by key:
a pack that hasn't been specialized yet shows as the pending splice — {"tag": int, **Kwargs@A}
lowering¶
each shape is hoisted to one module-level TypedDict class. those classes land ahead of
everything the module defines, and a field can name a class declared later or a type parameter
that only exists inside the generic class the literal was written in, so field types are emitted
as forward references:
a {**Kwargs} splice has no fields to erase to — they are only known at the specialization site,
and python erases type arguments anyway — so it lowers to extra_items="object"
scope¶
the rewrite fires only in annotation positions — function parameter and return annotations, variable annotations, and nested type arguments. dict expressions in value positions (real dicts) are never affected