Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

What is the idiomatic way of reusing key/values of TypedDict when total=False and True is needed? #2151

Answered by Daraan
andresdelfino asked this question in Q&A
Discussion options

Hi!

I construct a dictionary in several steps, and during that time I only want to guarantee that it doesn't have invalid key names or value types (total=False)).

Once the dictionary is constructed, I want to guarantee that it contains all the keys with the valid value types (total=True).

What is the idiomatic way to reuse the same key name/value type definition? Should I use the functional syntax and make two calls with the same dictionary specifying keys and values? If I understand correctly, inheritance is not an option.

You must be logged in to vote

You are correct, inheritance is not a supported way just like there is no switch from not required -> required. We lack a way to define a TypedDict partially.

Currently you have to define your TypedDict twice, once for each totality.
Functional vs. class definition does not make much of a difference. From you question I assume that you want to reuse a dict variable, e.g. Def = {"foo": int} and TypedDict("DefTotal", Def, total=True), while this would allow you to easily keep both dict types in sync it is not required for type-checkers to accept it (per PEP 589):

a variable that refers to a dictionary object does not need to be supported

Neither mypy not pyright allow dict variables and e...

Replies: 1 comment 2 replies

Comment options

You are correct, inheritance is not a supported way just like there is no switch from not required -> required. We lack a way to define a TypedDict partially.

Currently you have to define your TypedDict twice, once for each totality.
Functional vs. class definition does not make much of a difference. From you question I assume that you want to reuse a dict variable, e.g. Def = {"foo": int} and TypedDict("DefTotal", Def, total=True), while this would allow you to easily keep both dict types in sync it is not required for type-checkers to accept it (per PEP 589):

a variable that refers to a dictionary object does not need to be supported

Neither mypy not pyright allow dict variables and expect a literal dict for the functional syntax.

TL;DR: There is currently no supported way to reuse the key: value definitions and change between total <-> not total. You have two literally define both cases.


For completion, there is a possibility of a generic TypedDict[T: (Never, Missing)] where you make all keys required but with a Missing default value (some custom Sentinel), i.e keys are foo: int | T. Subscribing such a TypedDict with Never (complete: YourTD[Never]) gives all keys the correct type, i.e. complete["foo"] # -> int.
While it works it is less intuitive and you need to juggle around with subscriptions and/or some aliases.
Using Never as the default value for the type variable might also be a good idea but more risky than Missing which is the safer default to be recommended. If the sentinel type is annoying you can also substitute it with Any for the Any-Trick.

Code sample in pyright playground

from typing import TypedDict, Never, TypeAlias
from typing_extensions import Sentinel
MISSING = Sentinel("MISSING")
# Aliases for clarity
AllKeysPresent: TypeAlias = Never
MaybeMissingValues: TypeAlias = MISSING
# Never as constraint can be omitted, likewise total=True
class LockeableDict[T: (MaybeMissingValues, AllKeysPresent)](TypedDict, total=True):
 # Define all keys with T
 foo: int | T
PartialDict = LockeableDict[MaybeMissingValues]
FinalDict = LockeableDict[AllKeysPresent]
A: PartialDict = {"foo": MISSING} # all keys required
# make sure no missing keys 
# ...
B: FinalDict = A # <-- need to cast
reveal_type(A["foo"]) # int | MISSING
reveal_type(B["foo"]) # int
A["bar"] = 2 # error
You must be logged in to vote
2 replies
Comment options

Great, thank you!

Comment options

How about this?

for type_name, totality in ('Identity', True), ('PartialIdentity', False):
 globals()[type_name] = TypedDict(type_name, {'name': str, 'age': int}, total=totality)
Answer selected by andresdelfino
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /