3.12. Typing Variance
Covariance - Enables you to use a more derived type than originally specified
Contravariance - Enables you to use a more generic (less derived) type than originally specified
Invariance - Means that you can use only the type originally specified.
Invariance is important for example in
np.ndarray, where all items must be exactly the same type
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types [1]
In general, a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types.
3.12.1. SetUp
>>> from typing import TypeVar
3.12.2. Recap
>>> bool.mro()
[<class 'bool'>, <class 'int'>, <class 'object'>]
>>> isinstance(True, bool)
True
>>> isinstance(True, int)
True
>>> isinstance(True, object)
True
>>> isinstance(1, bool)
False
>>> isinstance(1, int)
True
>>> isinstance(1, object)
True
>>> isinstance(object, bool)
False
>>> isinstance(object, int)
False
>>> isinstance(object, object)
True
3.12.3. Invariance
Must be the same type
Means that you can use only the type originally specified. An invariant generic type parameter is neither covariant nor contravariant [1]
>>> T = TypeVar('T', bound=int, covariant=False, contravariant=False)
>>>
>>> def run(x: T):
... pass
>>> run(object) # error
>>> run(1) # ok
>>> run(True) # error
In the example above, we specified x: int, so invariant
will be run(1), others will yield an error.
3.12.4. Covariance
Must be the same type or it's subclass
Enables you to use a more derived type than originally specified [1]
>>> T = TypeVar('T', bound=int, covariant=True)
>>>
>>> def run(x: T):
... pass
>>> run(object) # error
>>> run(1) # ok
>>> run(True) # ok
3.12.5. Contravariance
Must be the same type or it's superclass
Enables you to use a more generic (less derived) type than originally specified [1]
>>> T = TypeVar('T', bound=int, contravariant=True)
>>>
>>> def run(x: T):
... pass
>>> run(object) # ok
>>> run(1) # ok
>>> run(True) # error
3.12.6. Use Case - 1
>>> Number = TypeVar('Number', int, float, covariant=True)
>>>
>>> def add(a: Number, b: Number) -> Number:
... return a + b
>>> class PositiveInteger(int):
... pass
>>> a = PositiveInteger(1)
>>> b = PositiveInteger(2)
>>>
>>> add(a, b) # ok (covariant - int|float or their subclasses, like: PositiveInteger)
3
3.12.7. Use Case - 2
>>> Number = TypeVar('Number', int, float, covariant=False, contravariant=False) # invariant
>>>
>>> def add(a: Number, b: Number) -> Number:
... return a + b
>>> class PositiveInteger(int):
... pass
>>> a = PositiveInteger(1)
>>> b = PositiveInteger(2)
>>>
>>> add(a, b) # error (invariant - must be int|float)
3