I’d be happy to take this on and finish it, if that works for you, @JelleZijlstra and @PIG208. I’m really excited about this upgrade and would like to keep contributing to CPython.
Backwards-compatibility paths (e.g. __extra_items__ special annotation).
The TypedDict[{...}] special form (_TypedDictSpecialForm), which the PEP does not specify.
Introduce typing.NoExtraItems as a C-level immortal singleton (mirroring NoDefault), to be exported via _typing.
Ensures process-wide singleton identity, consistency across typing, etc.
Update _TypedDictMeta and TypedDict to accept closed and extra_items, and set __closed__ and __extra_items__ on the resulting type.
Port the relevant tests from test_typing_extensions: test_closed_values, test_extra_items_class_arg, test_closed_inheritance, test_implicit_extra_items, test_cannot_combine_closed_and_extra_items, plus test_typed_dict_signature (adjusted to remove kwargs).
Update documentation, maybe in a separate PR?
Open question about closed:
Should the runtime error for whenclosed and extra_items are "used at the same time" also trigger when closed=False is passed explicitly, or only when closed=True?
Should the runtime error for whenclosed and extra_items are "used at the same time" also trigger when closed=False is passed explicitly, or only when closed=True?
There should be an error whenever both are specified at the same time, according to the section for closed:
It is a runtime error to use the closed and extra_items parameters in the same TypedDict definition.
Hi, coming a bit late as I see this issue has been closed a month ago, and hope it's not too late as 3.15 is not yet released, but I stumbled upon this problem while working on higher kinded types :
fromtypingimportTypedDict, NeverclassRealEmpty(TypedDict, closed=True):
passclassFalseEmpty(TypedDict, extra_items=Never):
passclassNotEmpty(TypedDict, closed=True):
x : Neverassertissubclass(NotEmpty, FalseEmpty) # should be okay per the docassertnotissubclass(NotEmpty, RealEmpty) # as well# yet RealEmpty and FalseEmpty should be equivalent per the docs
extra_items: indicates that the TypedDict has extra items. The argument must be a annotation expression specifying the type of the extra items. The type qualifier ReadOnly[] may be used to indicate that the extra items are read-only. Other type qualifiers are not allowed. If the extra items type is Never, no extra items are allowed, so this is equivalent to closed=True.
In particular, this is impactful for type checking in the following situation :
I think Never causes a lot of other weird behaviors here and there otherwise, and it's a shame it was never properly specified, but it is not the subject. My point is mainly that I think having extra_items=Never is not equivalent to having closed=True. Note also that RealEmpty and FalseEmpty are both inhabitated by only the empty dictionnary (as long as Never is uninhabitated), while NotEmpty is uninhabitated. Also, this makes RealEmpty and FalseEmpty equal types from a set-based type theory, but I also have arguments to say that set-based type theory is not what matches Python, namely there are uninhabitated types that are not Never.
I'm not entirely sure what the problem is here (and it's perhaps better discussed on Discourse). First, issubclass isn't meaningful for structural types. Did you mean to use the is_subtype relation from the typing spec?
I would argue that NotEmpty is equivalent to Never (because it has a required field of type Never, and therefore no values of it can exist). Therefore, it is indeed a subtype of every other TypedDict type, somewhat trivially. FalseEmpty and RealEmpty are indeed equivalent; I'm not sure why you think that NotEmpty is a subtype of only one of the two.
First, issubclass isn't meaningful for structural types. Did you mean to use the is_subtype relation from the typing spec?
Not sure about the difference, I'll look into it later, but you're probably right.
EDIT : After checking, I found :
structural types mostly include Protocols, TypedDict and Callable. Callable can be used in issubclass, so issubclass could be used on structural types. Code for demonstration :
Protocols can also benefit from issubclass thanks to @runtime_checkable, however limited to non-data Protocols. Fine, but TypedDict aren't Protocols, as Protocols can't be instantiated but TypedDicts can
issubclass doc does not state that it cannot be used with structural types (and this is normal, as it could, and need to, for virtual inheritance).
So I wouldn't say that it isn't meaningful, but more that either some features are not implemented or that it hasn't been made obvious why those features cannot be implemented (e.g. mentionning this in the unsupported operations section of the doc). I do understand that data Protocols aren't checkable "because some data attributes can be set on an instance in constructor and this information is not always available on the class object", and honestly it's fair to say it is the same about TypedDict. At the end of the day, indeed, I was talking about the subtyping between TypedDicts part of the documentation, irrelevantly of if it is implemented or not. The question here is more about type/documentation soundness.
Additionnally, I also think it's reasonable to ask who the subtypes of TypedDict are. Having this point of view might highlight interesting limits.
For example, applying structural subtyping to its core tells me that a subtype of a TypedDict type A is just any type with corresponding typing guarantees (e.g. throug overloads) on its __getitem__ method. In particular, from this point of view, it is meaningful to say that keys that are not specified (extra_items)/unspecifiable (closed=True) should return Never. But this has the very unwanted side effect of making dict[str, object] a structural type :
AllStrObj is indeed structural and theoretically equivalent to dict[str, object].
Because dict is not structural, I think there is an upper bound on the structural typing that can be applied to understand what can be assigned to TypedDicts. I haven't seen this detailed so far, and am very curious about it, as it might really clear up some limits of structural subtyping, which might help me define a more robust theory for sound higher kinded types.
I'm not sure why you think that NotEmpty is a subtype of only one of the two.
Because blindly applying the rules from the doc tells me so. More specifically, the answer of why I think so is that the doc states :
A TypedDict type B is a subtype of a TypedDict type A if and only if all of the conditions below are satisfied
[...]
If A is closed, B must also be closed, and it must not contain any items that are not present in A.
[...]
Here, NotEmpty structurally contains an item, x. Indeed, this item can never be instantiated, forcing NotEmpty to be uninhabitated. However, the following should reasonably typecheck but crash at runtime :
deffun(**kwargs : Unpack[NotEmpty]) ->None:
reveal_type(kwargs["x"]) # should reveal Never
while doing so on RealEmpty should fail at type checking, because RealEmpty has no key by definition.
On the other hand, by applying the same rules from the doc, it can be derived that NotEmpty is a subtype of FalseEmpty. Indeed, FalseEmpty being empty except from extra_items leads to trivial truth of universal properties and leaves only one condition :
Additionally, for any items in B that are not present in A, the item type must be a subtype of the extra items type in A.
Additionally, for any items in B that are not present in A, the item type must be equivalent to the extra items type in A.
Activity
angela-tarantula commented on Aug 17, 2025
I’d be happy to take this on and finish it, if that works for you, @JelleZijlstra and @PIG208. I’m really excited about this upgrade and would like to keep contributing to CPython.
Plan
Open question about closed:
PIG208 commented on Aug 17, 2025
There should be an error whenever both are specified at the same time, according to the section for closed:
angela-tarantula commented on Aug 17, 2025
Thanks. I'll prepare a PR!
JelleZijlstra commented on Aug 18, 2025
Yes, this is a separate PEP, which hasn't been accepted yet.
gh-137840: Implement PEP 728 (closed and extra_items in typing.TypedD…
hugovk commented on Jan 15, 2026
What's left to do for implementing PEP 728? At least docs and What's New are still needed.
StanFromIreland commented on Apr 17, 2026
A little ping @JelleZijlstra.
gh-137840: Document PEP 728 (#149207)
RobinSobczyk commented on May 30, 2026
Hi, coming a bit late as I see this issue has been closed a month ago, and hope it's not too late as 3.15 is not yet released, but I stumbled upon this problem while working on higher kinded types :
This comes from the fact that the doc states
In particular, this is impactful for type checking in the following situation :
I think Never causes a lot of other weird behaviors here and there otherwise, and it's a shame it was never properly specified, but it is not the subject. My point is mainly that I think having extra_items=Never is not equivalent to having closed=True. Note also that RealEmpty and FalseEmpty are both inhabitated by only the empty dictionnary (as long as Never is uninhabitated), while NotEmpty is uninhabitated. Also, this makes RealEmpty and FalseEmpty equal types from a set-based type theory, but I also have arguments to say that set-based type theory is not what matches Python, namely there are uninhabitated types that are not Never.
JelleZijlstra commented on May 30, 2026
I'm not entirely sure what the problem is here (and it's perhaps better discussed on Discourse). First, issubclass isn't meaningful for structural types. Did you mean to use the is_subtype relation from the typing spec?
I would argue that NotEmpty is equivalent to Never (because it has a required field of type Never, and therefore no values of it can exist). Therefore, it is indeed a subtype of every other TypedDict type, somewhat trivially. FalseEmpty and RealEmpty are indeed equivalent; I'm not sure why you think that NotEmpty is a subtype of only one of the two.
I do agree there are some problems with the way Never interacts with the gradual type system (https://discuss.python.org/t/interactions-with-never-leading-to-undesirable-assignability/99445), though I don't think PEP 728 makes those problems particularly more acute.
RobinSobczyk commented on May 30, 2026
Just to be sure, you meant there ?
Not sure about the difference, I'll look into it later, but you're probably right.
EDIT : After checking, I found :
So I wouldn't say that it isn't meaningful, but more that either some features are not implemented or that it hasn't been made obvious why those features cannot be implemented (e.g. mentionning this in the unsupported operations section of the doc). I do understand that data Protocols aren't checkable "because some data attributes can be set on an instance in constructor and this information is not always available on the class object", and honestly it's fair to say it is the same about TypedDict. At the end of the day, indeed, I was talking about the subtyping between TypedDicts part of the documentation, irrelevantly of if it is implemented or not. The question here is more about type/documentation soundness.
Additionnally, I also think it's reasonable to ask who the subtypes of TypedDict are. Having this point of view might highlight interesting limits.
For example, applying structural subtyping to its core tells me that a subtype of a TypedDict type A is just any type with corresponding typing guarantees (e.g. throug overloads) on its __getitem__ method. In particular, from this point of view, it is meaningful to say that keys that are not specified (extra_items)/unspecifiable (closed=True) should return Never. But this has the very unwanted side effect of making dict[str, object] a structural type :
AllStrObj is indeed structural and theoretically equivalent to dict[str, object].
Because dict is not structural, I think there is an upper bound on the structural typing that can be applied to understand what can be assigned to TypedDicts. I haven't seen this detailed so far, and am very curious about it, as it might really clear up some limits of structural subtyping, which might help me define a more robust theory for sound higher kinded types.
Because blindly applying the rules from the doc tells me so. More specifically, the answer of why I think so is that the doc states :
Here, NotEmpty structurally contains an item, x. Indeed, this item can never be instantiated, forcing NotEmpty to be uninhabitated. However, the following should reasonably typecheck but crash at runtime :
while doing so on RealEmpty should fail at type checking, because RealEmpty has no key by definition.
On the other hand, by applying the same rules from the doc, it can be derived that NotEmpty is a subtype of FalseEmpty. Indeed, FalseEmpty being empty except from extra_items leads to trivial truth of universal properties and leaves only one condition :
which is verified.