Search code examples
pythonmypypython-typingpython-3.10pylance

Why are TypedDict types without field and with NotRequired field incompatible?


I am trying to create a few functions which will return values of different TypedDict types. Most of fields in them will be same so I want to generate base dictionary with same function in all cases. However I am getting stumped by typing this correctly.

My idea was to create base type Parent and inherit from it, adding only NotRequired fields.

from typing_extensions import NotRequired, TypedDict

class Parent(TypedDict):
    parent_field: str


class Child(Parent):
    child_field: NotRequired[bool | None]


def create_parent() -> Parent:
    return {"parent_field": "example"}


child: Child = create_parent()
# Error:
# Expression of type "Parent" cannot be assigned to declared type "Child"
#  "child_field" is missing from "Type[Parent]"

However this fails since field child_field is missing, even though its type is NotRequired. Why it fails and how to evade this problem?

EDIT: I am using pylance (so pyright) for typechecking.

mypy (playground) gives similar error message:

Incompatible types in assignment (expression has type "Parent", variable has type "Child") [assignment]


Solution

  • This question is explained in PEP589 explicitly. Let me quote:

    A TypedDict type A with no key x is not consistent with a TypedDict type with a non-required key x, since at runtime the key x could be present and have an incompatible type (which may not be visible through A due to structural subtyping). Example:

    class A(TypedDict, total=False):
        x: int
        y: int
    
    class B(TypedDict, total=False):
        x: int
    
    class C(TypedDict, total=False):
        x: int
        y: str
    
     def f(a: A) -> None:
         a['y'] = 1
    
     def g(b: B) -> None:
         f(b)  # Type check error: 'B' incompatible with 'A'
    
     c: C = {'x': 0, 'y': 'foo'}
     g(c)
     c['y'] + 'bar'  # Runtime error: int + str
    

    So, your Parent class is not assignable to variable of type Child, and pylance points it out.