mypy 和 attrs:错误类型检查子类列表
mypy and attrs: errors typechecking lists of subclasses
我有一个消息容器,可以包含不同类型的消息。目前,只有短信。
这些是我的 类:
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"
这是为什么?
"Invalid type" 错误的原因是因为您正在尝试创建 generic class instead of a generic function。也就是说,您尝试创建一个 class 可以作为一个整体存储一些通用数据,而不是仅使单个函数或方法通用。
表面上的解决方法是修复你的 MessageContainer class 所以它是正确的通用的,就像这样:
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_types
—— 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_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 的子class,因此推断出 messages
的最合理类型大概是List[GenericMessage]
。所以在这种情况下,不需要注释。
我有一个消息容器,可以包含不同类型的消息。目前,只有短信。
这些是我的 类:
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"
这是为什么?
"Invalid type" 错误的原因是因为您正在尝试创建 generic class instead of a generic function。也就是说,您尝试创建一个 class 可以作为一个整体存储一些通用数据,而不是仅使单个函数或方法通用。
表面上的解决方法是修复你的 MessageContainer class 所以它是正确的通用的,就像这样:
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_types
—— 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_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 的子class,因此推断出 messages
的最合理类型大概是List[GenericMessage]
。所以在这种情况下,不需要注释。