Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lower bound for TypeVars #674

Open
poscat0x04 opened this issue Aug 27, 2019 · 7 comments
Open

Lower bound for TypeVars #674

poscat0x04 opened this issue Aug 27, 2019 · 7 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@poscat0x04
Copy link

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

@gvanrossum
Copy link
Member

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.

@poscat0x04
Copy link
Author

poscat0x04 commented Aug 28, 2019

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.

@gvanrossum
Copy link
Member

(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. :-)

@ilevkivskyi
Copy link
Member

This feature appears from time to time in various contexts, see e.g. python/mypy#3737. So I can see the value in it, but I don't think it is a priority ATM.

@JukkaL
Copy link
Contributor

JukkaL commented Aug 29, 2019

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.

@erp12
Copy link

erp12 commented Apr 5, 2021

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:

  1. B is a sub-type of V and thus the return type would be ImmutableMap[K, V].
  2. B is a super-type of V and thus the return type would be ImmutableMap[K, B].
  3. B is some other type with no relationship to V and thus the return the must fall back on ImmutableMap[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.

@Galbar
Copy link

Galbar commented Aug 19, 2021

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?

@srittau srittau added the topic: feature Discussions about new features for Python's type annotations label Nov 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

7 participants