Skip to content Skip to sidebar Skip to footer

Mypy And Attrs: Errors Typechecking Lists Of Subclasses

I have a message container that can contain different kinds of messages. For now, there are only text messages. These are my classes: from typing import List, TypeVar import attr

Solution 1:

The reason for the "Invalid type" error is because you are attempting to create a generic class instead of a generic function. That is, instead of making just a single function or method generic, you're trying to create a class that can as a whole store some generic data.

The superficial fix for this is to just repair your MessageContainer class so it's properly generic, like so:

from typing import Generic

# ...snip...@attr.s(auto_attribs=True)
classMessageContainer(Generic[GMessage]):messages: List[GMessage] = attr.ib()

    defoutput_texts(self) -> None:""" Display all message texts in the container """for message inmessages:
            print(message.text)

This would end up fixing the error you described up above.

However, this is probably not the solution you want to use -- the issue is that instead of creating a MessageContainer that can contain multiple different kinds of messages, you've instead created a MessageContainer that can be parameterized to a specific kind of method.

You can see this for yourself by including adding a call to the reveal_types(...) pseudo-function:

messages = [
    TextMessage(text='a', comment='b'),
    TextMessage(text='d', comment='d'),
]

container = MessageContainer(messages=messages)
reveal_type(container)

(No need to import reveal_types from anywhere -- mypy special-cases that function).

If you run mypy against this, it'll report that container has a type of MessageContainer[TextMessage]. This means your container wouldn't be able to accept any other kind of message in the future. Maybe this is what you want to do, but based on your description above, probably not.


I would recommend instead doing one of two following things.

If your MessageContainer is meant to be read-only (e.g. after you construct it, you can no longer add new messages to it), just switch to using Sequence. If your custom datastructure is meant to be read-only, then it's fine to also use a read-only stuff internally:

@attr.s(auto_attribs=True)
class MessageContainer:

    messages: Sequence[GenericMessage] = attr.ib()

    def output_texts(self) -> None:
        """ Display all message texts in the container """
        for message in messages:
            print(message.text)

If you do want to make your MessageContainer writable (e.g. maybe add an add_new_message method), I would recommend instead that you actually fix the call-sites of MessageContainer to do this:

@attr.s(auto_attribs=True)
class MessageContainer:

    messages: List[GenericMessage] = attr.ib()

    def output_texts(self) -> None:
        """ Display all message texts in the container """
        for message in messages:
            print(message.text)

    def add_new_message(self, msg: GenericMessage) -> None:
        self.messages.append(msg)

# Explicitly annotate 'messages' with 'List[GenericMessage]'messages: List[GenericMessage] = [
    TextMessage(text='a', comment='b'),
    TextMessage(text='d', comment='d'),
]

container = MessageContainer(messages=messages)

Normally, mypy infers that messages is of type List[TextMessage]. Passing that into a writable container that expects a List[GenericMessage] would be unsound for the reasons I explained in my previous answer to you -- e.g. what if MessageContainer tries appending a message that isn't a TextMessage?

So, what we can do instead is promise to mypy that messages will never be used as a List[TextMessage] and will instead always be used as a List[GenericMessage] -- this makes the types line up, guarantees subsequent code can't misuse your list, and satisfies mypy.

Note that you wouldn't need to add this annotation if you tried adding more message types to the list. For example, suppose you added a 'VideoMessage' type to your list:

messages = [
    TextMessage(text='a', comment='b'),
    TextMessage(text='d', comment='d'),
    VideoMessage(text='a', link_to_video='c'),
]

container = MessageContainer(messages=messages)

In this case, mypy would inspect the contents of messages, see that it contains multiple subclasses of GenericMessage, and so infer that the most reasonable type of messages is probably List[GenericMessage]. So in this case, no annotation would be necessary.

Post a Comment for "Mypy And Attrs: Errors Typechecking Lists Of Subclasses"