pythongenericspython-typing

How to declare Python constraint on generic type to support __lt__?


In the Python 3.5 code below, I want to use a less than operator (<) to compare two generic values. How can I declare a constraint on T to support __lt__?

from typing import *
import operator 

T = TypeVar('T')

class MyList(Generic[T]):
    class Node:
        def __init__(self, k:T) -> None:
            self.key = k 
            self.next = None  # type: Optional[MyList.Node]

    def __init__(self) -> None:
        self.root = None # type: Optional[MyList.Node]

    def this_works(self, val:T) -> bool:
        return self.root.key == val 

    def not_works(self, val:T) -> bool:
        return operator.lt(self.root.key, val)

I'm using Mypy to type check and it's failing on not_works with the message:

$ mypy test.py
test.py: note: In member "not_works" of class "MyList":
test.py:20: error: Unsupported left operand type for < ("T")

Other languages support constraints on T.

In C#: class MyList<T> where T:IComparable<T>

In Java: class MyList<T extends Comparable<? super T>>


Solution

  • You can achieve your goal by passing an extra parameter bound to TypeVar, as described in PEP484:

    A type variable may specify an upper bound using bound=<type>. This means that an actual type substituted (explicitly or implicitly) for the type variable must be a subtype of the boundary type. A common example is the definition of a Comparable type that works well enough to catch the most common errors:

    Sample code from mentioned PEP:

    from typing import TypeVar
    
    class Comparable(metaclass=ABCMeta):
        @abstractmethod
        def __lt__(self, other: Any) -> bool: ...
        ... # __gt__ etc. as well
    
    CT = TypeVar('CT', bound=Comparable)
    
    def min(x: CT, y: CT) -> CT:
        if x < y:
            return x
        else:
            return y
    
    min(1, 2) # ok, return type int
    min('x', 'y') # ok, return type str
    

    In latest version (verified with 0.521) of mypy, the scenario above is correctly handled.