pythonpython-typingordereddictionarypyright

How to annotate type of an `OrderedDict` that is initialized with literals?


Suppose I have the following:

from collections import OrderedDict
from dataclasses import dataclass


@dataclass
class HelloWorld:
    x: OrderedDict[str, int]


a = OrderedDict([("a", 0), ("c", 2), ("b", 1)])
HelloWorld(a) <--- # type error here

The type error produced is:

Argument of type "OrderedDict[Literal['a', 'c', 'b'], Literal[0, 2, 1]]" cannot be assigned to parameter "x" of type "OrderedDict[str, int]" in function "__init__"
  "OrderedDict[Literal['a', 'c', 'b'], Literal[0, 2, 1]]" is incompatible with "OrderedDict[str, int]"
    Type parameter "_KT@OrderedDict" is invariant, but "Literal['a', 'c', 'b']" is not the same as "str"
    Type parameter "_VT@OrderedDict" is invariant, but "Literal[0, 2, 1]" is not the same as "int

Oddly enough, this very similar snippet does not produce an error:

from collections import OrderedDict
from dataclasses import dataclass


@dataclass
class HelloWorld:
    x: OrderedDict[str, int]


HelloWorld(OrderedDict([("a", 0), ("c", 2), ("b", 1)])) # <--- no error

Solution

  • cast is often a viable solution, but here you can just annotate the initial variable. That will preserve the type safety of your code (unlike cast which can cast a str to dict[int, int] equally well). Pyright should have not complain about the following:

    from collections import OrderedDict
    from dataclasses import dataclass
    
    @dataclass
    class HelloWorld:
        x: OrderedDict[str, int]
    
    a: OrderedDict[str, int] = OrderedDict([("a", 0), ("c", 2), ("b", 1)])
    HelloWorld(x=a)
    

    And here's a playground.

    This error is caused by overly optimistic inference of pyright together with OrderedDict invariance (as any MutableMapping).

    As @InSync noticed in comments, on Python 3.7 and above you can build OrderedDict from a dict literal still preserving the order, and inference is less specific in that case:

    a = OrderedDict({"a": 0, "c": 2, "b": 1})
    HelloWorld(x=a)