1

It looks like a dictionary of SomeKeyType cannot be assigned as a dictionary of objects, even though all types are subtypes of object:

x: dict[str, str] = {"a": "b"}
y: dict[object, str] = x
Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "Dict[object, str]")

Copying doesn't help either:

x: dict[str, str] = {"a": "b"}
y: dict[object, str] = dict(x)
Argument 1 to "dict" has incompatible type "Dict[str, str]"; expected "SupportsKeysAndGetItem[object, str]"

A similar error occurs when updating a dictionary of objects with a dictionary of another type:

x: dict[object, str] = {"a": "a"}
y: dict[str, str] = {"b": "b"}

x.update(y)
x = {**x, **y}
x |= y
Argument 1 to "update" of "dict" has incompatible type "Dict[str, str]"; expected "Mapping[object, str]"
Argument 1 to "update" of "dict" has incompatible type "Dict[str, str]"; expected "Mapping[object, str]"
Argument 1 to "__ior__" of "dict" has incompatible type "Dict[str, str]"; expected "Mapping[object, str]"

Note that a naive implementation of dict.update does not generate any typing issue:

for k, v in y.items():
    x[k] = v

Is that on purpose or is it an issue I should report on the mypy tracker?

Vincent
  • 12,919
  • 1
  • 42
  • 64

1 Answers1

2

While a str is an object, it's not generally true that an F[str] is an F[object] for some type constructor F. For example, a dict[str, ...] is apparently not a dict[object, ...]. This is likely because dict[K, V] is invariant in its key type K, whereas you'd need it to be covariant. Types are often invariant in a type parameter because the type appears in both method argument and return type positions, like dict.__getitem__(key: K) -> V and dict.keys() -> KeysView[K].

You can fix your code by typing x as dict[object, str]. I guess this works because mypy then infers the "a" as an object, as opposed to inferring the dict[str, str] as a dict[object, str]. Whether that's the best solution for you depends on your context.

See this section of the mypy docs for more info on in-/co-/contravariance.

This discussion looks relevant though I only skimmed it.

joel
  • 6,359
  • 2
  • 30
  • 55
  • Interesting, thanks for the references! Still, the copy version should have worked, right? Same thing for the `update` method: it fails in the same way when updating a `dict[object, ...]` with a `dict[str, ...]`, even though the explicit loop-and-set-items approach works perfectly fine. – Vincent Aug 24 '21 at 13:26
  • @Vincent the copy method may be typed to return exactly what is passed in, in which case there's no difference – joel Aug 24 '21 at 13:59
  • @Vincent I'd have to see some code re update and loop-and-set to see what you mean – joel Aug 24 '21 at 14:44
  • I updated my post to include some examples of conflicting types when updating a dictionary. – Vincent Aug 24 '21 at 14:59
  • @Vincent I think the other forms that don't work probably fall into the same category as above. The loop works because you're adding bare `str`s and `object`s which do satisfy the subtyping relationship – joel Aug 24 '21 at 16:58