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 help(SomeClass)
or SomeClass?
in a Jupyter notebook, the covariance/contravariance information should be visible. Previously, this was achieved by the T_co
, T_contra
naming convention. However, since PEP 695 does not use this convention, I presume the intention is to deprecate it.
Proposal
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.TypeVar
mechanism 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.