Search code examples
pythonlistsubclasspython-typing

Python typing for a subclass of list


I want to be able to define what the contents of a subclass of list have to be. The class would look like the following.

class A(list):
   def __init__(self):
      list.__init__(self)

I want to include typing such that the following would happen.

import typing

class A(list: typing.List[str]):  # Maybe something like this
   def __init__(self):
      list.__init__(self)

>> a = A()
>> a.append("a")  # No typing error
>> a.append(1)  # Typing error

Solution

  • typing conveniently provides a generic version of collections.MutableSequence, so something to the effect of:

    import typing
    
    T = typing.TypeVar('T')
    class HomogeneousList(typing.MutableSequence[T]):
        def __init__(self, iterable: typing.Iterable[T]=()) -> None:
            self._data: typing.List[T]  = []
            self._data.extend(iterable)
    
        @typing.overload
        def __getitem__(self, index: int) -> T: ...
        @typing.overload
        def __getitem__(self, index: slice) -> HomogeneousList[T]: ...
        def __getitem__(self, index):
            return self._data[index]
    
        @typing.overload
        def __setitem__(self, index: int,  item: T) -> None: ...
        @typing.overload
        def __setitem__(self, index: slice, item: typing.Iterable[T]) -> None: ...
        def __setitem__(self, index, item):
            self._data[index] = item
    
        def __delitem__(self, index: typing.Union[int, slice]) -> None:
            del self._data[index]
    
        def __len__(self) -> int:
            return len(self._data)
    
        def insert(self, index: int, item: T) -> None:
            self._data.insert(index, item)
    
    
    string_list = HomogeneousList[str]()
    string_list.append('foo')
    string_list.append(42)
    
    
    int_list = HomogeneousList[int]()
    int_list.append(42)
    int_list.append('foo')
    

    Now, mypygives the following errors:

    test.py:36: error: Argument 1 to "append" of "MutableSequence" has incompatible type "int"; expected "str"
    test.py:41: error: Argument 1 to "append" of "MutableSequence" has incompatible type "str"; expected "int"
    

    There is some tricky aspects of typing __getitem__ etc because they accept slice objects as well, but not terrible.

    Note, this is useful, because if you just try to do:

    class HomogeneousList(collections.abc.MutableSequence, typing.Generic[T]):
        ....
    

    MyPy, at least, doesn't throw an error for append. AFAIKT you'd have to explicitly add:'

    def append(self, item: T) -> None:
        self._data.append(item)
    

    Which sort of removes a lot of the utility of collections.abc.MutableSequence to begin with. Anyway, thankfully, typing provides generic versions of all of these out of the box!

    Note, you can use these generically, like I've show, but you can also do something like:

    class StringList(HomogeneousList[str]):
        pass
    
    mylist = StringList([1,2,3]) # mypy error
    mylist = StringList('abc') # no error
    
    mylist.append('foo') # no error
    mylist.append(42) # mypy error