-
Notifications
You must be signed in to change notification settings - Fork 287
-
Assume you have a "constant" dictionary:
A_DICT = { 'a': 1, 'b': 2, }
Because dictionaries are mutable, and because the type checker does not know that you do not intend to add or remove items from the dictionary, its type will be inferred as dict[str, int] - the broadest type possible. What would be some way to instruct type checkers to use the narrowest type possible, as they would do for e.g. a tuple - to derive a hypothetical TypedDict from a constant dict? (I assume Pyright at least does something like this internally since it is able to provide code completion forA_DICT.)
More generally, would it make sense to have some way to annotate symbols partially, instructing the type checker to fill in the rest? For example, if I wanted to mark A_DICT as immutable for type-checking purposes, I'd be forced to paremetrise it in full: Mapping[str, int]. What if, instead, I was able to say Infer[Mapping] or Infer[TypedDict] - has something like this been considered or explored in the past?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 4 comments 6 replies
-
One of the main problems is that dict is mutable:
A_DICT = { 'a': 1, 'b': 2, } reveal_type(A_DICT) A_DICT.update(OTHER) reveal_type(A_DICT)
What should both reveal?
Beta Was this translation helpful? Give feedback.
All reactions
-
reveal_type would reveal the same type, which is the original type of A_DICT. A variable's type only changes through redefinition (that is, reassignment). I'm not suggesting that the dictionary's type should be re-inferred on the fly, only that there should be some way for it to be inferred strictly at definition time (cf. TypeScript's "as const", but unlike TS this could be extended to other types: Mapping, a hypothetical TypedMapping, or even TypeVarTuple-backed protocols).
Beta Was this translation helpful? Give feedback.
All reactions
-
I assume Pyright at least does something like this internally
No, pyright doesn't ever infer a TypedDict from a dict expression (unless it's using bidirectional type inference and the "expected type" is declared as a TypedDict). Its completion suggestions in this case come from some clever hackery where it rummages around in the parse tree within the same code file to find assignments of a dict expression, and it pulls the key names out of it. In other words, it's not using type information directly here.
The problem with inferring a TypedDict for a constant that is assigned a dict expression is that TypedDict is not a subtype of dict[str, Any], so it cannot be used in cases where a caller expects to receive this type. That would generate false positives in the general case.
It would be better if the developer could explicitly say "I'd like this variable to be inferred as a TypedDict rather than a normal dict".
What is a convenient way to do this without having to define the full TypedDict? Here are a few ideas.
- I recently added exploratory support to pyright for inlined
TypedDictdefinitions based on this thread in the typing-sig.
A_DICT: dict[{"a": int, "b": int}] = ...
This is still admittedly cumbersome because it duplicates the keys and type information.
- We could specify that when
TypedDictis used as a type annotation by itself in an assignment statement, it should signal the type checker that the intent is to infer aTypedDict.
A_DICT: TypedDict = ...
- We could use some other shortcut based on the inlined TypedDict proposal, like an empty dict could imply "any
TypedDict", and a type checker could then infer the type of a constant as a "final"TypedDictwith specific keys.
A_DICT: dict[{}] = ...
If there is support for the inlined TypedDict idea, I recommend that someone write it up as a PEP and start the process to standardize it. This same PEP could address the issue discussed in this thread. If no PEP emerges (or is ultimately rejected), I will remove the experimental support for inlined TypedDict from pyright.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 6
-
I would expect using:
A_DICT : Final = {
'a': 'a',
'b': 'b',
}
to infer that A_DDICT is:
TypedDict:
a: Literal['a']
b:Literal['b']
This is not the case (At least not in my setup)
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 3
-
I undesatnd that it depends on what final means. You can update the dict without reassigning anything. Maybe we should have some freeze keyword xD
Beta Was this translation helpful? Give feedback.
All reactions
-
Even better have some runtime guaranties:
A_DICT=freeze({
'a': 'a',
'b': 'b',
})
the above should infer A_DICT is:
FrozenTypedDict:
a: Literal['a']
b:Literal['b']
would be the only way to get a frozen dict which cannot be updated at run time
FrozenTypedDict would not have an update method
Beta Was this translation helpful? Give feedback.
All reactions
-
consider using types.MappingProxyType
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
The idea of having immutable dictionaries builtin in python the same way that list vs tuple exist is interesting though (not just as a typing but in runtime)
Beta Was this translation helpful? Give feedback.
All reactions
-
Beta Was this translation helpful? Give feedback.
All reactions
-
For anyone coming here in 2025 (or later): As PEP 764 (inlined TypedDicts) is currently being discussed, I took the liberty of raising the issue of inferring the type of constant dicts.
Beta Was this translation helpful? Give feedback.