typing
typing copied to clipboard
Lower bound for TypeVars
Lower bounds are useful when implementing type safe methods of (classes that are covariant with some type) that take argument of some other type. For example, the method updated of scala's Seq
reference: #59
Can you elaborate with an example? I'm not going to try and understand the Scala docs, and there's too much discussion in #59 to know what specifically you meant.
from typing import *
T = TypeVar("T", covariant=True)
V = TypeVar("V")
class List(Generic[T]):
def updated(self, v: V) -> List[V]: # V should be a super type of T
"""
Creates a new list with the first element updated
"""
x, *xs = self
return [v] + xs
The argument type cannot be T since it would then occur in the contravariant position.
(A little confusing since List is a PEP 484 standard type that's invariant, but I guess Sequence would work just as well, and I guess you come from a different culture.)
OK, so assuming int is a subtype of float (mypy treats it that way), If we had x: List[int], then x.updated(3.14) would be a List[float], while if we had y: List[float] then y.updated(42) should not be treated as a List[int] because it may still contain floats.
How common do you think this use case would be in real-world Python code? (I'm not interested in alternative realities where Python is a functional language. :-)
This feature appears from time to time in various contexts, see e.g. https://github.com/python/mypy/issues/3737. So I can see the value in it, but I don't think it is a priority ATM.
Yeah, this would be nice every once in a while. This might help with some list methods, such as copy(), but only together with F-bounded quantification. I don't understand the updated example above. Maybe a more complete example with an implementation would make things clearer.
I agree that this is pretty low priority. We'd probably need to change typing.TypeVar to allow specifying a lower bound.
I just hit this issue while building some custom data structure types on top of the popular pyrsistent library. I was creating custom immutable list and set types that are covariant in the element type. Without lower type bounds, I could not logically implement (at least) the following methods:
- List:
append,prepend,concat - Set:
add,union,intersect - Both:
reduce
To provide a better example (not using the invariant List type) let's consider the following example using the built-in Mapping which is covariant in the value type.
from typing import *
K = TypeVar("K")
V = TypeVar("V", covariant=True)
B = TypeVar("B")
class ImmutableMap(Mapping[K, V]):
def updated(self, key: K, value: B) -> "ImmutableMap[K, ?]":
""" Returns a new `ImmutableMap` where the `key` is associated with a new `value`."""
...
The question is: what is the return type of updated? There are only a few possible scenarios:
Bis a sub-type ofVand thus the return type would beImmutableMap[K, V].Bis a super-type ofVand thus the return type would beImmutableMap[K, B].Bis some other type with no relationship toVand thus the return the must fall back onImmutableMap[K, Any].
Since we are discussing typed collections here, scenario 3 is very rare and probably irrelevant.
Scenario 1 and 2 are both logical and (in my experience) fairly common. If we could declare B = TypeVar("B", lower_bound=A) then it would be possible to support scenarios 1 and 2 in a single method signature without losing any type information.
Without lower bounds, we can only support scenario 1 by annotating value: V which is misaligned with the method's logical semantics.
I understand this might stay low-priority. I just wanted to hopefully clear up some of the motivation and document a specific use case that hit this limitation.
I'm having a similar problem. I've written a dependency injector for python (I know the code is horrible) and I'm adding type hints to its code so that it passes mypy checks. I have a method with the signature like this:
def bind(self, types: Tuple[Type[T], ...], instance: S, name: Optional[str] = None, singleton: bool = False) -> None:
The idea being that types is a tuple of types that instance can be represented as (all(isinstance(instance, t) for t in types)).
Is there any way this relationship between T and S can be defined?