基类和继承类的类型注释- 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]的类型。

所以我阅读了泛型类型,在我看来,解决方案是使用TypeVars和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(...)

但是为什么它沉默了-我没有给我任何新的信息?我想得越多,Generic似乎越不像是正确的选择,因为事情是:NodePropertiesNode的所有实例都将具有与self完全相同类型的self.children

但是如果我从class Node(Generic[N]):中删除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]"

(注意:reveal_type是一个MyPy可以识别的函数,但是如果你试图在运行时使用它,那将会失败。

要使MyPy将未参数化的泛型标记为错误,请使用命令行参数--disallow-any-generics运行MyPy。这样做意味着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。我认为在这种情况下没有必要使用泛型

为了用TypeVar注释__init__中的self参数,不需要从泛型中继承。此外,正如您在问题中所说,从Generic继承在这里并不真正有意义,无论是从MyPy的角度还是从其他人阅读您的代码的角度。我会这样修改你的代码:
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)

现在我们有了让我高兴的注释,即使在最严格的设置下,它们甚至对人类也有意义!

注意:我在代码中更改了另外两个东西:

  1. 我将*args, **kwargs参数添加到您的__init__方法中-正如所写的,它们违反了Liskov替换原则。通过添加这些参数,可以避免这个问题。
  2. 我将您的测试从if inherit更改为if inherit is not None-很多事情都可以在python中使用False-y,因此在测试值是否为None时,通过身份进行测试要安全得多。

相关内容

最新更新