mypy和attrs:错误类型检查子类列表

2024-10-01 15:48:15 发布

您现在位置:Python中文网/ 问答频道 /正文

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

这些是我的课程:

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协议

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

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


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

错误是:

error: Invalid type "GMessage"

为什么呢


Tags: texttrue消息messageautocommentclassattr
1条回答
网友
1楼 · 发布于 2024-10-01 15:48:15

“无效类型”错误的原因是您试图创建generic class而不是generic function。也就是说,您尝试创建一个类,作为一个整体可以存储一些泛型数据,而不是使单个函数或方法成为泛型的

表面上的修复方法是修复MessageContainer类,使其具有适当的泛型,如下所示:

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)

这将最终修复您上面描述的错误

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

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

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

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

(无需从任何地方导入reveal_typesmypy特例以实现该功能)

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


我建议你做以下两件事中的一件

如果您的MessageContainer是只读的(例如,在构建它之后,您不能再向其中添加新消息),只需切换到使用序列即可。如果您的自定义数据结构是只读的,那么也可以在内部使用只读内容:

@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方法),我建议您实际修复MessageContainer调用站点,以实现此目的:

@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推断messages属于List[TextMessage]类型。将其传递到一个需要List[GenericMessage]的可写容器中是不合理的,因为我在之前的回答中已经解释了一些原因,例如,如果MessageContainer尝试附加一条不是TextMessage的消息怎么办

因此,我们可以做的是向mypy承诺messages永远不会用作List[TextMessage],而是始终用作List[GenericMessage]。这使类型排列整齐,保证后续代码不会误用列表,并满足mypy的要求

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

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的最合理类型可能是List[GenericMessage]。因此,在这种情况下,不需要注释

相关问题 更多 >

    热门问题