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
mypythoughMutableSequenceprotocol yourself. I would not inherit fromlist, asAshould be usable anywhere an ordinarylistis, and that means potentially accepting things likemyAobject.append("foo"). HaveAinclude alistas an instance variable instead instead of inheriting fromlist, and let the defined methods enforce theint-only restriction.Ais supposed to be a list ofints. The Liskov substitution principle says that anywhere I might want to use an instance oflist, I should be able to use an instance ofAin its place. But ifAis (e.g.) supposed to be a list ofints, that means the following is invalid:a = A(); a.append("foo"). Alistis a mutable sequence of arbitrary objects;Ais not, so it should not be a subclass oflist. It doesn't matter if the two classes have the same interface, because the semantics are very different.