Correctly typing nested TypedDicts

I am attempting to create the typing for a TypeDict which will be nested. See the example code below:

from typing import TypedDict

NodeDict = TypedDict('NodeDict', {
    'id': str,
    'value': int,
    'parent': None | 'NodeDict' # Error occurs here
})

In this example, I am showing the typing for a Node which can be used in something like a binary Tree. Thus, the Node needs to be able to reference a parent Node and the type of the 'parent' field should be NodeDict, otherwise you get the following error:

'parent': None | NodeDict # Error occurs here
NameError: name 'NodeDict' is not defined

As this is in the definition of NodeDict the type NodeDict does not already exist and I can’t use parent: None | NodeDict, thus I thought that I could use parent: None | 'NodeDict', as one needs to do often in a class which references a type of itself.

I am however getting an error:

'parent': None | 'NodeDict' # Error occurs here
TypeError: unsupported operand type(s) for |: 'NoneType' and 'str'

I want predictions and inteli-sense to work for this if I were to access my_example_node['parent'], thus it would need to know that that is also of type NodeDict.

Currently, I simply replaced the code with 'parent': None | Any, which then works but as expected the suggestions by my IDE are then not correct and the type is not correctly inferred.

I created a gist of this snippet to make things easier.

FYI, you can still use PEP-604 syntax even if your annotation contains a forward reference, but you have to put the whole expression in quotes, i.e.:

from typing_extensions import TypedDict

class NodeDict(TypedDict):
    id: str
    value: int
    parent: 'NodeDict | None'

(This is documented here.)

Alternatively, you can use from __future__ import annotations, which will mean that all annotations in your module are implicitly “stringified”:

from __future__ import annotations
from typing_extensions import TypedDict

class NodeDict(TypedDict):
    id: str
    value: int
    parent: NodeDict | None

(Note: PEP-563 is no longer planned to become the default behaviour for Python, but until there’s a replacement, it’s still pretty useful.)

1 Like

Thank you, for this. This documentarion lists the specific feature and why the error occured.

From the documentation:


Note The | operand cannot be used at runtime to define 
unions where one or more members is a forward 
reference. For example, int | "Foo", where "Foo" is a 
reference to a class not yet defined, will fail at runtime. 
For unions which include forward references, present the 
whole expression as a string, e.g. "int | Foo".

1 Like