Hi all, I would like to get the conversation going on introducing a mechanism to have dictionary literals be inferred as TypedDicts. The issue has been discussed here and there a few times (see the links below) but to my knowledge no attempt has been made yet to flesh out the details, let alone reach consensus. I would like to change that. ![]()
The issue:
The type of
foo = {
"a": 1,
"b": 2,
}
is currently being inferred as dict[str, int] by e.g. Mypy and Pyright, but there are many cases in which foo’s keys will not change and one would therefore like to have its type to be inferred more narrowly as
TypedDict[{
"a": Literal[1],
"b": Literal[2],
}]
without having to write out the full type as type annotation.
Here, for brevity I am using the proposed (PEP 764) syntax for inline TypedDicts to describe a closed typed dictionary (in the sense of PEP 728), but the question of how to infer foo as TypedDict shouldn’t depend on whether or not PEP 764 gets accepted or not.
The use case for TypedDict inference comes up frequently when using dictionary literals to build & return structured data with a fixed set of keys “on the fly”. In such cases, defining separate dataclass/TypedDict types first is often particularly undesirable because there will only ever be a single instance of the data structure in the first place (namely the dictionary literal) and so the type declaration would always follow the dictionary literal’s structure, anyway, and have to be kept in sync.
Prior discussions:
- github.com/python/typing: Strict inference of constant dicts
- discuss.python.org: PEP 764: Inlined typed dictionaries – comment #76 (and responses)
Prior art:
- Enabling narrow type inference for a given dictionary is somewhat similar to TypeScript’s
as constsyntax, whose behavior I outlined here.
Existing ideas:
So far I have come across the following ideas:
- In the Github issue mentioned above, @erictraut discusses using a “bare”
TypedDictannotationfoo: TypedDict = { … }to trigger TypedDict inference. (Another idea being mentioned isfoo: dict[{}] = { … }; however the former seems more consistent with PEP 764.) - In PEP 764: Inlined typed dictionaries - #82 by davidfstr @davidfstr proposes introducing a a very similar type annotation
TypedDict[...](with a literal...), which would trigger narrow TypedDict inference for the annotated variable or function.
In my opinion, introducing such a type annotation alone comes with one significant downside, though, particularly compared to TypeScript’s as const: It would be impossible to guide type inference for dictionary literals within more complex, nested expressions, without spelling out the entire expression’s type. Consider e.g.:
foo = (
"hello",
"world",
{
"a": 1,
"b": 2,
}
)
Here, foo is currently inferred as Tuple[Literal["hello"], Literal["world"], dict[str, int]] but we would want to infer Tuple[Literal["hello"], Literal["world"], TypedDict[{ … }]] without having to annotate foo with Tuple[Literal["hello"], Literal["world"], TypedDict] or something similar. I have elaborated on this topic here.
For this reason, my thoughts are going in the direction of introducing syntax (or at least a helper function in typing) to guide type inference at the expression level, i.e. to annotate expressions, e.g.:
foo = (
"hello",
"world",
{
"a": 1,
"b": 2,
} as TypedDict
)
The use case might be broader than inferring TypedDict alone: For instance, I might have a list or tuple literal which I’d like to infer as Sequence or a dictionary literal which I’d like to infer as Mapping. In fact, the use case might also be broader than literal expressions: I might have an expression of type A which I want the type checker to treat as type B while still ensuring that the expression is compatible with type B. This would be reminiscent of TypeScript’s satisfies, while also doing a type cast.
Maybe these are two separate topics, though? Maybe not? Either way, I’d be grateful for feedback and very interested in hearing your thoughts!