8000 "Sealed" `TypedDict`s that don't allow subtypes which introduce additional keys · Issue #1984 · python/typing · GitHub
[go: up one dir, main page]

Skip to content
"Sealed" TypedDicts that don't allow subtypes which introduce additional keys #1984
Closed as duplicate
@sh-at-cs

Description

@sh-at-cs

Proposal

It might be useful if there was a possibility to mark a TypedDict as "sealed" (other naming suggestions welcome!), so that dicts with additional keys are not considered subtypes. E.g.:

from typing import TypedDict

class A(TypedDict, sealed=True):
  a: int

class AB(TypedDict):
  a: int
  b: int

ab: AB = {"a": 1, "b": 2}
a: A = ab  # error!

An alternative syntax may be to allow making any already-defined TypedDict sealed by wrapping it in Sealed[...]. E.g.:

class A(TypedDict):
  a: int

a: Sealed[A] = ab  # error!

Motivation

Situation

Consider trying to update() a TypedDict instance with an instance of a "partial" variant that only contains some of the former's keys:

from typing import TypedDict

class AB(TypedDict):
  a: int
  b: int

class A(TypedDict):
  a: int

ab: AB = {"a": 1, "b": 2}
a: A = {"a": 3}

ab.update(a)

Current type checking behavior

Both major type checkers will complain about this:

Mypy Pyright

error: Argument 1 to update of TypedDict has incompatible type A; expected TypedDict({'a': int, 'b'?: int}) [typeddict-item]

error: No overloads for update match the provided arguments (reportCallIssue)
error: Argument of type A cannot be assigned to parameter __m of type Partial[AB] in function update
  b is an incompatible type
    object is incompatible with int (reportArgumentType)

As can be seen from the error messages, the reason is that A allows subtypes which would have a key b just like AB, but the type of the value for that key could be different in these hypothetical subtypes from what it is in AB.

Current workaround & why it's insufficient

This issue can be worked around by adding all additional keys of AB as NotRequired keys on A:

from typing import NotRequired

class A(TypedDict):
  a: int
  b: NotRequired[int]

This will make ab.update(a) pass type checking.

But that is quite hacky:

  • If I have several different TypedDicts like AB, with different extra keys (beyond those in A), I have to introduce separate variants of A for each of them just to make update work correctly.
  • If AB has many more keys than A, I have to repeat all of them.
  • "Philosophically", it doesn't seem right that A should have any knowledge of AB's keys just to make the update operation work.

By contrast, if "sealed" TypedDicts were possible, I would only need the one sealed A and that's it.

Related proposals

  • @sealed decorator
    • This has less in common with this proposal than the name might suggest and I only bring it up at all because the name is the same: The @sealed decorator would apply to classes and forbid subclasses beyond those found in the same module. Meanwhile, this proposal is only about individual TypedDicts, shares none of the module-scoping logic, and can be seen as introducing a new kind of structural type, whereas @sealed is about nominal types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0