python - 基类和继承类的类型注释 - Generic 和 TypeVar 是正确的方法吗?
问题描述
说我有一个基类
from typing import List, Optional
class Node:
def __init__(self, name: str) -> None:
self.name = name
self.children: List['Node'] = []
...
和一个子类
class PropertiesNode(Node):
def __init__(self, name: str, properties: List[str], inherit: Optional['PropertiesNode']) -> None:
Node.__init__(self, name)
self.properties = set(properties)
if inherit:
self.properties.update(inherit.properties)
self.children = deepcopy(inherit.children)
for child in self.children:
child.properties.update(properties) # ERR: "Node" has no attribute "properties" [attr-defined]
如您所见, mypy (正确地)在那里标记了一个错误,因为Node.children
它明确给出了List[Node]
.
所以我阅读了泛型类型,在我看来解决方案是使用TypeVar
s 和Generic
:
from typing import Generic, List, Optional, TypeVar
N = TypeVar('N', bound='Node')
P = TypeVar('P', bound='PropertiesNode')
class Node(Generic[N]):
def __init__(self: N, name: str) -> None:
self.name = name
self.children: List[N] = []
class PropertiesNode(Node[P]):
def __init__(self: P, name: str, properties: List[str], inherit: Optional[P]) -> None:
Node.__init__(self, name)
self.properties = set(properties)
if inherit:
self.properties.update(inherit.properties)
self.children = deepcopy(inherit.children)
for child in self.children:
child.properties.update(properties)
但是,现在当我实例化类时,我得到
foo = Node("foo") # ERR Need type annotation for "foo" [var-annotated]
bar = PropertiesNode("bar", ["big", "green"], None) # ERR Need type annotation for "bar" [var-annotated]
现在,我可以通过这样做使这些静音
foo: Node = Node("foo")
bar: PropertiesNode = PropertiesNode(...)
但为什么这让它沉默 - 我没有在那里给 mypy 任何新信息?我想得越多,Generic
似乎就越不是正确的选择,因为事情是:Node
or的所有实例都PropertiesNode
将具有self.children
与self
.
但是,如果我删除Generic[N]
from class Node(Generic[N]):
,我最终会再次出现原始错误:
class PropertiesNode(Node):
...
child.properties.update(properties) # ERR "N" has no attribute "properties" [attr-defined]
解决方案
这里发生了两件事
1. 泛型
注释一个变量foo: Node
,其中Node
是一个泛型类,相当于将它注释为foo: Node[typing.Any]
。它会在默认设置下使 MyPy 静音,但如果您选择使用 MyPy 并设置一些更严格的标志True
(我建议这样做!),您会发现 MyPy 仍然将这种事情标记为错误。
如果你在 MyPy 中运行它:
from typing import TypeVar, Generic, List
N = TypeVar('N', bound='Node')
class Node(Generic[N]):
def __init__(self: N, name: str) -> None:
self.name = name
self.children: List[N] = []
foo: Node = Node("foo")
reveal_type(foo)
您会发现 MyPy 会返回给您类似以下的消息:
Revealed type is "__main__.Node[Any]"
(NBreveal_type
是 MyPy 识别的一个函数,但如果你尝试在运行时使用它会失败。)
要让 MyPy 将未参数化的泛型标记为错误,请使用命令行参数运行 MyPy --disallow-any-generics
。这样做意味着 MyPy 将标记以下错误:
main.py:3: error: Missing type parameters for generic type "Node"
main.py:10: error: Missing type parameters for generic type "Node"
强制您将代码调整为以下内容:
from typing import TypeVar, Generic, List, Any
N = TypeVar('N', bound='Node[Any]')
class Node(Generic[N]):
def __init__(self: N, name: str) -> None:
self.name = name
self.children: List[N] = []
foo: Node[Any] = Node("foo")
这让 MyPy 再次高兴,并说出了您在原始代码中所说的相同内容,但更明确。
然而...
2.我认为在这种情况下没有必要使用泛型
您不必为了用. _ 此外,正如您在问题中所说,无论是从 MyPy 的角度还是从其他阅读您的代码的人的角度来看,从这里继承都没有真正意义。我会像这样修改你的代码:self
__init__
TypeVar
Generic
from typing import List, Optional, TypeVar, Any
from copy import deepcopy
N = TypeVar('N', bound='Node')
P = TypeVar('P', bound='PropertiesNode')
class Node:
def __init__(self: N, name: str, *args: Any, **kwargs: Any) -> None:
self.name = name
self.children: List[N] = []
class PropertiesNode(Node):
def __init__(self: P, name: str, properties: List[str], inherit: Optional[P], *args: Any, **kwargs: Any) -> None:
super().__init__(name)
self.properties = set(properties)
if inherit is not None:
self.properties.update(inherit.properties)
self.children: List[P] = deepcopy(inherit.children)
for child in self.children:
child.properties.update(properties)
现在我们有了可以让 MyPy 开心的注释,即使是在最严格的设置下,它们甚至对人类也有意义!
注意我在这里更改了代码中的另外两件事:
- 我
*args, **kwargs
在你的方法中添加了参数__init__
——正如所写的那样,它们违反了Liskov 替换原则。通过添加这些参数,您可以避免该问题。 - 我将您的测试从更改
if inherit
为if inherit is not None
- python 中的很多东西都可以是False
-y,因此在测试值是否存在时,通过身份进行测试要安全得多None
。
推荐阅读
- java - 您将如何解决“httpservlet 无法解决类型”错误?
- reactjs - 为什么我的复选框在 ReactJS 中取消选中后不返回
- reactjs - 在其父 const React js 之外访问嵌套的 const
- java - Java 11 升级后 Eclipse Spring Tools Suite 缺少 JDK 警告
- pandas - 使用列信息将数据框重塑为新的单列
- r - 如何在 dplyr 的 purrr 映射中使用动态变量
- java - Axis 1.4 SimpleDeserializer 在试图反序列化的东西中遇到了一个子元素,这不是预期的
- python - 将大端字节数组转换为 int,如 python 中的 struct.unpack
- amazon-web-services - 在 Docker 映像中使用 Spark-Submit?
- android - HMS 地图工具包 - 调用 getMapAsync() 后未调用 onMapReady()