python 3.7.0 mypy 0.641 extending pythons' list with UserList?

Viewed 778

I am trying to extend pythons’ list with some custom methods, for that I am creating an class inheriting from UserList.

I am not sure what is the right way and I would like to get mypy play nicely with UserList.

I consulted cpython UserList docs and searched inside mypy for UserList but couldn’t find anything.

Using:

  • mypy 0.641
  • python 3.7.0

This is a minimal example of what I am trying to achive

from collections import UserList
from typing import List, Optional, Union


class A:
    ...


class B:
    ...


class C:
    ...


TMessage = Union[A, B, C]


class MyList(UserList):
    """Minimal example"""

    def __init__(self, data: Optional[List[TMessage]] = None) -> None:
        self.data: List[TMessage] = []
        if data:
            self.data = data[:]

    def get_last(self) -> TMessage:
        return self.data[-1]

    # other methods to be added ...


some_data = [A(), B(), C(), C(), B(), A()]
my_list_a = MyList(some_data)
my_list_b = MyList(some_data)

my_list_b = my_list_a[3:]

Mypy complains as follows

~/tmp ❯❯❯ mypy mypy_userlist.py

mypy_userlist.py:34: error: Argument 1 to "MyList" has incompatible type "List[object]"; expected "Optional[List[Union[A, B, C]]]"
mypy_userlist.py:35: error: Argument 1 to "MyList" has incompatible type "List[object]"; expected "Optional[List[Union[A, B, C]]]"
mypy_userlist.py:37: error: Incompatible types in assignment (expression has type "MutableSequence[Any]", variable has type "MyList")

I could add # type: ignore to the conflicting lines but I would like to avoid that.

What is the right way to extend the python’s list with custom methods and get mypy happy?

1 Answers

I'm fairly new at MyPy but I think you have two issues. The first is that lists are mutable, so although your list object some_data satisfies the required structure in your code, there's no reason that an object, not of type A, B or C couldn't be added to it later, meaning at compile tile, Mypy can't ensure that

my_list_a = MyList(some_data) 

is a valid assignment. (have a look at the common issues section of the Mypy docs here for more discussion)

You can fix this by explicitly annotating some_data:

some_data : List[TMessage] = [A(), B(), C(), C(), B(), A()]

The second problem will pop up when you fix this, when you try and assign your two lists using slicing. MyPy won't know what your slice function will return and will complain about incompatible types.

To fix this, you can explicitly implement the slice functionality into your class.

def __getitem__(self, slice_indices) -> 'MyList':
     return self.data[slice_indices]
Related