Reputation: 1011
I have a function that accepts a class that derives from NamedTuple
and converts it into a schema. However when I run MyPy on the following code it fails with Argument 1 to "to_schema" has incompatible type "Type[Foo]"; expected "Type[NamedTuple]"
from typing import NamedTuple, Type
def to_schema(named_tuple: Type[NamedTuple]):
pass
class Foo(NamedTuple):
pass
to_schema(Foo)
Is there a way to properly type the code so that it typechecks with MyPy?
Edit:
Python documentation states that Type[Foo]
accepts any subclasses of Foo
(https://docs.python.org/3/library/typing.html#typing.Type). I have multiple subclasses of NamedTuple
, for entities in our data model, so I'm looking for a way to annotate the function in a way that would typecheck.
Upvotes: 3
Views: 1563
Reputation: 64278
The root issue with your code is that NamedTuple
is not an actual type -- it's actually just a special sort of "type constructor" that synthesizes an entirely new class and type. E.g. if you try printing out the value of Foo.__mro__
, you'll see (<class '__main__.Foo'>, <class 'tuple'>, <class 'object'>)
-- NamedTuple
is not present there at all.
That means that NamedTuple
isn't actually a valid type to use at all -- in that regard, it's actually a little surprising to me that mypy just silently lets you construct Type[NamedTuple]
to begin with.
To work around this, you have several potential approaches:
Rather then using Type[NamedTuple]
, use either Type[tuple]
or Type[Tuple[Any]]
.
Your Foo
genuinely is a subtype of a tuple, after all.
If you need methods or fields that are specifically present only in namedtuples, use a custom protocol. For example, if you particularly need the _asdict
method in namedtuples, you could do:
from typing_extensions import Protocol
class NamedTupleProto(Protocol):
def _asdict(self) -> Dict[str, Any]: ...
def to_schema(x: Type[NamedTupleProto]) -> None: pass
class Foo(NamedTuple):
pass
to_schema(Foo)
Note that you will need to install the typing_extensions
third party library to use this, though there are plans to formalize Protocols and add it to Python at some point. (I forget if the plan was Python 3.7 or 3.8).
Add a type ignore or a cast on the call to to_schema
to silence mypy. This isn't the greatest solution, but is also the quickest.
For related discussion, see this issue. Basically, there's consensus on the mypy team that somebody ought to do something about this NamedTuple thing, whether it's by adding an error message or by adding an officially sanctioned protocol, but I think people are too busy with other tasks/bugs to push this forward. (So if you're bored and looking for something to do...)
Upvotes: 2