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