Should total=False mean all keys are NotRequired by default?

Currently, when retrieving TypedDict.__annotations__ with a base that has total=False, you need to compare with __optional_keys__ to ensure the keys are not required. Since total=False means that keys are not required by default, does it make sense to annotate them as such beforehand? Otherwise __annotations__ ends up with keys that are annotated as NotRequired and Required as well as keys without NotRequired from total=False, which seems odd?

Semantically total=False means the same as annotating every key with NotRequired.

Are you suggesting that __annotations__ should be transformed automatically to add NotRequired[] to every key in that case? I would oppose that; in my experience, it’s generally better to keep processing of things like __annotations__ to a minimum at runtime. That way, it’s easier for any tools that want to know exactly what the user wrote. For example, if the runtime has a bug and puts the wrong thing in __annotations__, such tools might not know what to do any more.

1 Like

In that case, would you expect get_type_hints to return the total=False keys annotated correctly?

I think there’s a difference there between what I expect and what I want. I expect get_type_hints() do all kinds of weird things to the types (turning None into NoneType?). I want it to not do that. Therefore, I have learned to avoid using it.

My main concern now is that if we change get_type_hints(), that’s likely to break some users. Those users might then get upset at me as I’m one of the maintainers of, and I don’t like it when users get upset at me. Also, if we make get_type_hints() more complicated, that makes it harder to maintain in the future.


Come to think of it, is there a formal reason for why None becomes type(None)? I went sleuthing around for the commit history and didn’t find anything definitive for this behaviour.

None is such a common annotation we made an exception for it in the syntax, letting users write x: None instead of x: NoneType.

But we assumed that this irregularity would be annoying when inspecting annotations at runtime – it’d be more consistent if the runtime types to always be instances of type. I think we were wrong there, and runtime introspectors are more likely to be surprised than delighted by this choice. But it’s too late to change.