Mypy type inferred for a list as a value in a dict

I have this

D1 = {'a': [], 'b': 'bbb'}
L1 = D1['a'] 
L1.append(222)  # mypy error: "object" has no attribute "append"  [attr-defined]

It seems to be that mypy takes the type of L1 to be Sequence[str]

It doesn’t complain for the following, but it seems convoluted just to appease mypy.

D2 = {'a': (L2 := []), 'b': 'bbb'}
L2.append(333)  # OK

Questions:

  1. Is there a recommended way to make mypy infer that the type is a list or list[Any]?

L1 = cast(list[Any], D1['a']) also works, but this is not so much inferring that being told the type.

  1. Why does it assume Sequence[str] as the type? Is it because it computed a type for D1 to be dict[str, Sequence[str]], with Sequence[str] being the least common type of the values in the definition of D1 that it could guess?

The error doesn’t say “Sequence” but “object”. This tells me mypy infers D1 to be dict[str, object].

You have a value-heterogeneous dictionary. You’d need to declare the variable holding it either as a dict with a union of value types, or as a TypedDict.
Or turn it into a class.

1 Like

Hello,

what version of Python are you using?

I am using Pycharm and have v3.13 as the interpreter and I don’t experience any issues. It works just fine.

The OP’s question regards type checker behavior, not the interpreter/runtime behavior.

I forgot to quote the mypy message from within vscode. Maybe due to context or perhaps flags that they run it with, the type that it infers for L1 is Sequence[str].

Ok, sure, I could declare the type of the D1 explicitly. In my case the values will be only str | list[Any].

Dicts do not have per-key types. D1 has type dict[str, Sequence[str]] because str is the narrowest type (in some sense) that includes both 'a' and 'b, while Sequence[str] is similarly the narrowest type that includes both [] and 'bbb'. D1[k] will have static type Sequence[str] no matter what (str) value k has.

If you want D1['a'] to have type list, then you can use typing.TypedDict, which lets you specify per-key types, while restricting you to a fixed set of str keys.

class MyDict(typing.TypedDict):
    a: list[str]
    b: str

D1: MyDict = {'a': [], 'b': 'bbb'}

reveal_type(D1['a'])  # list[str]
reveal_type(D1['b'])  # str

Literals present a problem for type inference. Assuming a sufficiently rich library of available types, what type should 2 have?

  1. int
  2. natural
  3. postive_int
  4. even
  5. Literal[2]

Similary, what type should {'a': [], 'b': 'bbb'} have? Is it a dict with string keys? With exactly 2 keys? With 'a' and 'b' specifically as keys? If the keys are part of the type, is 'a' bound to a list of strings? integers? other dicts? Is the length of 'bbb' significant?

It’s a balancing act between inferring a type that prevents values you intended to be ignored, but still allows values “not in evidence”, so to speak. mypy and PyRight demonstrate different attitudes towards this balance, for example. While mypy infers dict[str, Sequence[str]], PyRight infers dict[str, Unknown], not assuming that [] and 'bbb' should share some “tight” covering type. An even simpler disagreement: given x = 3, mypy infers int, while PyRight infers Literal[3].

(Which is a rambling way to say: you don’t want to infer an overly strict or an overly broad type for a single value, and the more complex the value, the wider a range of types you have to choose from. Type annotation is useful even when inference is available.)

1 Like