Today I took a deep dive into Liskov's Substitution Principle and covariance/contravariance.
And I got stuck on the difference between:
T = TypeVar("T", bound=Union[A, B])
T = TypeVar("T", A, B, covariant=True)
My Understanding of #1
Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])
This answer clearly states T
can be :
Union[A, B]
(or a union of any subtypes ofA
andB
such asUnion[A, BChild]
)A
(or any subtype ofA
)B
(or any subtype ofB
)
This makes perfect sense to me.
My Flawed Understanding of #2
MyPy doesn't allow constrained TypeVar's to be covariant? Defining a generic dict with constrained but covariant key-val types
Re-mentions the bound=Union[A, B]
case, but does not get at the meaning of option #2, A, B, covariant=True
.
I have tried playing around with mypy
, and can't seem to figure it out.
Can anyone point out what this means?
I think it means:
A
(or any subtype of A
)B
(or any subtype of B
)(aka it excludes the Union
case from above)
**Edit**
It was asked in the comments:
Are you sure that they're actually different?
Here's sample code to show the difference. The errors come from mypy==0.770
.
from typing import Union, TypeVar, Generic
class A: pass
class ASub(A): pass
class B: pass
# Case 1... Success: no issues found
# T = TypeVar("T", bound=Union[A, B])
# Case 2... error: Value of type variable "T" of "SomeGeneric" cannot be "ASub"
T = TypeVar("T", A, B, covariant=True)
class SomeGeneric(Generic[T]): pass
class SomeGenericASub(SomeGeneric[ASub]): pass
**Edit 2**
I ended up asking about this in python/mypy #8806: Generic[T_co] erroring when T_co = TypeVar("T_co", A, B, covariant=True) and passed subclass of A
This cleared up some misunderstandings I had. Turns out TypeVar("T", A, B, covariant=True)
isn't really correct, knowing the value restrictions A
and B
aren't actually covariant.
Use of covariant=True
syntax is only helpful when they're related.
It is a type variable. Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions.
Python is a dynamically typed language. This means that the Python interpreter does type checking only as code runs, and that the type of a variable is allowed to change over its lifetime.
Covariance and contra-variance are terms that relate to the intersection between object orientation and generics.
Here's the question this concept is trying to answer:
Base
and
Derived
.List<T>
.List<Derived>
can be used wherever List<Base>
can?List<Base>
can be used wherever List<Derived>
can?If the answer to (3) is yes, it's called covariance and we'll say declare List
as having covariance=True
. If the answer to (4) is true, it's called 'contra-variance'. If none is true, it's invariant.
Bounds also come from the intersection of OO and generics. When we define a generic type MyType - does it mean that 'T' can be any type at all? Or, may I impose some limitations on what T might be? Bounds allow me to state that the upper bound of T is, for example, the class Derived
. In that case, Base
can't be used with 'MyType' - but Derived
and all its subclasses can.
The definition of covariance and contravariance can be found in this section of PEP-484.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With