# Correct mutable version of typing.Mapping

AFAICT, when we want covariance the suggestion is to use typing.Mapping[KT, VT_co] instead of dict[KT, VT]. However, typing.Mapping is read-only. The mutable version typing.MutableMapping is invariant instead of covariant. Is there a type that is covariant and mutable?

No, thatâ€™s just not valid. Reread the documentation of co and contra variance.

If you think you have an example where it can would be somehow save to have a mutable non-invariant container type, please show it.

No, because this is a contradiction in terms. Covariance means that we could treat â€ścontainer of derivedâ€ť like a subtype of â€ścontainer of baseâ€ť. But mutability, for â€ścontainer of baseâ€ť, means that we can add a base instance to it - whether or not thatâ€™s also a derived instance. And subtyping, by the Liskov Substitution Principle, means that we can do anything with a subtype that we could do with the base type. To be both covariant and mutable, therefore, weâ€™d have to be allowed to add non-derived elements to a container of derived elements (because a container of base elements lets us do that). But that contradicts the definition of a container.

(I have to look up which one is â€ścovarianceâ€ť and which one is â€ścontravarianceâ€ť every time - but after that the logic flows naturally.)

1 Like

No, because this is a contradiction in terms.

I donâ€™t think so. Iâ€™m looking for covariance only in the VT, i.e., almost the same as typing.MutableMapping[KT, VT] but is instead [KT, VT_co], not the full mutable mapping. The KT and the Mapping part is not required to be covariant.

The use case I have in mind is a function f(â€¦)->Map[KT, VT1] and be able to assign the return value to a Map[KT, VT2] variable m, where VT1 is a subtype of VT2 (hence covariance in VT is desired) and be able to mutate m[k]=some VT2 values for some k in KT, without having to introduce additional intermediate variables or wrap in dict(â€¦) | {k: â€¦} gymnastic just to get round the typing system.

``````def f() -> Map[str, int]: ...

x: Map[str, object] = f()
x['foo'] = object()
``````

Explain to me how that is not unsafe? You inserted an `object` into a `Map` which was supposed to contain `int`, if someone else has a reference to the same map their code will break because they canâ€™t rely on the map only containing `int` anymore.

The only reason why `Mapping` is covariant in `VT` is exactly because this cannot happen, i.e. you canâ€™t downcast and then insert new values in an existing `Mapping` that may be shared with someone else.

You could define a mutable mapping thatâ€™s covariant in `VT` if the only mutations you are allowing are `clear`, `popitem` etcâ€¦ but I donâ€™t think thatâ€™s going to help you very much. (In simple terms any method where the `VT` appears in the parameter list, is forbidden with a covariant type parameter, the only exception to that rule is `__init__`)

1 Like