Andrej Palicka
Andrej Palicka

Reputation: 1011

Mypy doesn't typecheck function with Type[NamedTuple]

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

Answers (1)

Michael0x2a
Michael0x2a

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:

  1. Rather then using Type[NamedTuple], use either Type[tuple] or Type[Tuple[Any]].

    Your Foo genuinely is a subtype of a tuple, after all.

  2. 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).

  3. 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

Related Questions