I have several namedtuples that share some fields. I have a function that accepts these tuples and is guaranteed to only interact with the shared fields. I want to typecheck such code in mypy.
An example of the code would be:
from typing import NamedTuple
class Base(NamedTuple):
x: int
y: int
class BaseExtended(NamedTuple):
x: int
y: int
z: str
def DoSomething(tuple: Base):
return tuple.x + tuple.y
base = Base(3, 4)
base_extended = BaseExtended(5, 6, 'foo')
DoSomething(base)
DoSomething(base_extended)
When I run mypy on this code I get a predictable error:
mypy_example.py:20: error: Argument 1 to "DoSomething" has incompatible type "BaseExtended"; expected "Base"
Is there no way to structure my code and keep mypy typechecking? I cannot inherit BaseExtended from Base, since there's a bug in the NamedTuple inheritance implementation:
https://github.com/python/typing/issues/427
I don't want to use an ugly "Union[Base, BaseExtended]" either, since this breaks when I try to typecheck a List, since "List[Union[Base, BaseExtended]]" is not equal to "List[BaseExtended]" due to some mypy magic about variant/covariant types:
https://github.com/python/mypy/issues/3351
Should I just abandon the idea?
There is PEP 544 that proposes an extension to type system that will allow structural subtyping (static duck typing). Also runtime implementation of
typing.NamedTuple
will be improved soon, probably in Python 3.6.2 end of June (this will be also backported viatyping
on PyPI).The way named tuples are constructed make inheritance from
typing.NamedTuple
classes as yet not possible. You'd have to write your own metaclass to extend thetyping.NamedTupleMeta
class to make subclassing work, and even then the class generated bycollections.namedtuple()
is just not built to extend.Instead, you want to use the new
dataclasses
module to define your classes and achieve inheritance:The module is new in Python 3.7 but you can
pip install dataclasses
the backport on Python 3.6.The above defines two immutable classes with
x
andy
attributes, with theBaseExtended
class adding one more attribute.BaseExtended
is a full subclass ofBase
, so for typing purposes fits the requirements for theDoSomething()
function.The classes are not full named tuples, as they don't have a length or support indexing, but that's trivially added by creating a baseclass that inherits from
collections.abc.Sequence
, adding two methods to access fields by index:MyPy will soon support
dataclasses
explicitly; in version 0.600 you'll get errors still as it doesn't recognise thedataclasses
module import or that a__new__
method is generated.In Python 3.6 and earlier, you can also install the
attrs
project to achieve the same effects; the above sequence base class looks like this usingattrs
:dataclasses
is directly based onattrs
, withattrs
providing more functionality; mypy fully supports classes generated withattrs
.