PEP 695 introduced a new type parameter syntax, where the variance of parameters is supposed to be implicitly deducted by the type checker. (PEP 695 – Type Parameter Syntax | peps.python.org)
I see 2 main reasons why giving the option to explicitly express the desired variance is benefitial:
Reason 1. Implicit variance can be overwritten by accident. Author A might write a class with the intent that it is covariant in a certain parameter
T. Later, Author B adds an extra method to the class, inadvertently making it invariant in
T. This has downstream effects to user
C who imports the class. This change might pass silently if there are no checks to verify the covariance. Being able to explicitly state the covariance is an elegant way to have this automatically verified by the type-checker.
Reason 2. Self-explanatory documentation. When generating documentation with a tool like
sphinx, or simply calling
SomeClass? in a Jupyter notebook, the covariance/contravariance information should be visible. Previously, this was achieved by the
T_contra naming convention. However, since PEP 695 does not use this convention, I presume the intention is to deprecate it.
Explicit is better than implicit.
The suggestion is to use the unary plus
__pos__ and minus
__neg__ operator to create explicit covariance annotation:
class ClassA[T1, -T2, +T3](list[T1]): def method1(self, a: T2) -> None: ... def method2(self) -> T3: ...
Here, we explicitly mark
T2 as contravariant and
T3 as covariant. Later, if someone were to add a method to
ClassA that violates this variance, we get direct feedback from the type-checker. The plus and minus symbol are already widely used for this purpose in type-theory literature.
In regard to explicit variance, the PEP authors write (PEP 695 – Type Parameter Syntax | peps.python.org):
We considered adding syntax for specifying whether a type parameter is intended to be invariant, covariant, or contravariant. The
typing.TypeVarmechanism in Python requires this. A few other languages including Scala and C# also require developers to specify the variance. We rejected this idea because variance can generally be inferred, and most modern programming languages do infer variance based on usage. Variance is an advanced topic that many developers find confusing, so we want to eliminate the need to understand this concept for most Python developers.
However, the point here is to make it an optional feature, which can be used to increase explicitness of the annotation and in order to avoid having to add extra tests that validate the variance assumptions.
Note: Annotations without prefix would be treated as specified by PEP695: in particular, there is no explicit annotation for invariant types. These are superfluous, since adding variance to an invariant type only widens the acceptable replacement types.