1

In mypy, how would you specify that a type Generic over T has methods that are only valid if T meets certain conditions?

For example, if we made a custom collection class with a min method, returning the smallest element in the collection:

from typing import Generic, TypeVar

T = TypeVar("T")

class MyCollection(Generic[T]):
    
    def __init__(self, some_list: List[T]):
        self._storage = some_list

    def min(self) -> T: # This requires that T implements __lt__
        "Get the smallest element in the collection"
        return min(self._storage)     

How can you tell the type system that calling min on MyCollection of T is only allowed if T implements __lt__?

So basically I'd like to have some methods of a generic container only be valid if extra protocols are met.

-- Useful links --

You can see from the typehints in the standardlib for min that they've defined a protocol for enforcing __lt__ is implemented

class SupportsLessThan(Protocol):
    def __lt__(self, __other: Any) -> bool: ...

SupportsLessThanT = TypeVar("SupportsLessThanT", bound=SupportsLessThan)  # noqa: Y001
Georgy
  • 12,464
  • 7
  • 65
  • 73
Oliver Rice
  • 768
  • 8
  • 20
  • 1
    I don't think the python type system supports that – juanpa.arrivillaga Nov 30 '20 at 21:10
  • Just to be clear: Do you want the entire class to only accept ``T``'s that support ``<``, or do you want only the specific method to accept only such ``T``'s? In the latter case, Do you expect the method to be entirely absent or merely to not return (raise an error)? – MisterMiyagi Dec 01 '20 at 11:09

1 Answers1

2

In the same stub file you linked, look at the type hints for list.sort:

class list(MutableSequence[_T], Generic[_T]):
    ...
    @overload
    def sort(self: List[SupportsLessThanT], *, key: None = ..., reverse: bool = ...) -> None: ...
    @overload
    def sort(self, *, key: Callable[[_T], SupportsLessThan], reverse: bool = ...) -> None: ...

By type hinting self, you can specify that a method is only applicable for certain specializations of a generic class. You can also see this documented in the mypy docs.

Your min would thus be annotated as

def min(self: 'MyCollection[SupportsLessThanT]') -> SupportsLessThanT:
    ...

with a suitable definition of SupportsLessThanT.

user2357112
  • 260,549
  • 28
  • 431
  • 505