Python mypy和attrs:错误类型检查子类列表
我有一个消息容器,可以包含不同类型的消息。目前,只有短信 这些是我的课程:Python mypy和attrs:错误类型检查子类列表,python,mypy,Python,Mypy,我有一个消息容器,可以包含不同类型的消息。目前,只有短信 这些是我的课程: 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
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"
这是为什么?出现“无效类型”错误的原因是您试图创建一个而不是一个。也就是说,您尝试创建一个类,作为一个整体可以存储一些泛型数据,而不是使单个函数或方法成为泛型的
表面上的修复方法是修复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
您可以通过添加对discover\u types(…)
伪函数的调用来了解这一点:
messages = [
TextMessage(text='a', comment='b'),
TextMessage(text='d', comment='d'),
]
container = MessageContainer(messages=messages)
reveal_type(container)
(无需从任何地方导入discover_类型
——mypy特例即可发挥作用)
如果对此运行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\u new\u 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推断消息
属于列表[TextMessage]
类型。将其传递到一个需要列表[GenericMessage]
的可写容器中是不合理的,因为我在前面的回答中已经解释了一些原因——例如,如果MessageContainer
尝试附加一条非文本消息,该怎么办
因此,我们可以做的是向mypy承诺,消息
永远不会用作列表[TextMessage]
,而是始终用作列表[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将检查
消息的内容,查看它是否包含多个子类的GenericMessage,从而推断最合理的消息类型可能是列表[GenericMessage]
。因此,在这种情况下,不需要任何注释。这说明了很多问题!我以前确实看到显式注释有助于消除这个错误,但现在我明白了为什么它会这样做。谢谢你抽出时间!现在mypy还有另一个问题,这次不是使用attrs
生成的类,而是使用django模型:)您能看一下吗?