mypy 和 attrs:错误类型检查子类列表 [英] mypy and attrs: errors typechecking lists of subclasses

查看:59
本文介绍了mypy 和 attrs:错误类型检查子类列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个可以包含不同类型消息的消息容器.目前,只有短信.

I have a message container that can contain different kinds of messages. For now, there are only text messages.

这些是我的课程:

from typing import List, TypeVar

import attr


@attr.s(auto_attribs=True)
class GenericMessage:
    text: str = attr.ib()


GMessage = TypeVar('GMessage', bound=GenericMessage)


@attr.s(auto_attribs=True)
class TextMessage(GenericMessage):

    comment: str = attr.ib()


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

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

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

这个想法是消息不仅可以接受文本消息,还可以接受任何其他消息,所有这些都共享容器将使用的相同GenericMessage协议.

The idea is that messages can accept not only text messages but any other messages, all of which share the same GenericMessage protocol which will be used by the container.

因此,在进行类型检查时,mypy 在此用法中显示错误:

So, when typechecking, mypy shows an error at this usage:

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


container = MessageContainer(messages=messages)
container.output_texts()

错误是:

error: Invalid type "GMessage"

这是为什么?

推荐答案

无效类型"错误的原因是因为您正在尝试创建一个 通用类 而不是 通用函数.也就是说,不是将单个函数或方法设为泛型,而是尝试创建一个可以作为一个整体存储一些泛型数据的类.

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.

对此的表面修复只是修复您的 MessageContainer 类,使其具有适当的通用性,如下所示:

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)
class MessageContainer(Generic[GMessage]):

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

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

这最终会修复您在上面描述的错误.

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

然而,这可能不是您想要使用的解决方案——问题在于,您没有创建可以包含多种不同类型消息的 MessageContainer,而是创建了一个 MessageContainer,它可以包含多种不同类型的消息.可以参数化为特定类型的方法.

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.

您可以通过添加对 reveal_types(...) 伪函数的调用来亲眼看到这一点:

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)

(无需从任何地方导入 reveal_types - mypy 的特殊情况).

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

如果您对此运行 mypy,它会报告 container 的类型为 MessageContainer[TextMessage].这意味着您的容器将来将无法接受任何其他类型的消息.也许这就是您想要做的,但根据您上面的描述,可能不是.

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.

如果您的 MessageContainer 是只读的(例如,在您构造它之后,您不能再向其添加新消息),只需切换到使用 Sequence.如果您的自定义数据结构是只读的,那么也可以在内部使用只读的东西:

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)

如果您确实想让您的 MessageContainer 可写(例如,可能添加一个 add_new_message 方法),我会建议您实际修复 call-sitesMessageContainer 来做到这一点:

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)

通常,mypy 推断 messagesList[TextMessage] 类型.由于我在之前对您的回答中解释的原因,将其传递到期望 List[GenericMessage] 的可写容器将是不合理的 - 例如如果 MessageContainer 尝试附加不是 TextMessage 的消息怎么办?

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?

因此,我们可以做的是向 mypy 承诺 messages 永远不会用作 List[TextMessage] 而是始终用作 List[GenericMessage]——这使得类型对齐,保证后续代码不会滥用你的列表,满足mypy.

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.

请注意,如果您尝试向列表中添加更多消息类型,则不需要添加此注释.例如,假设您在列表中添加了VideoMessage"类型:

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)

在这种情况下,mypy会检查messages的内容,看到它包含GenericMessage的多个子类,因此推断出最合理的messages类型可能是列表[GenericMessage].所以在这种情况下,不需要注释.

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.

这篇关于mypy 和 attrs:错误类型检查子类列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆