基类和继承类的类型注释 - Generic 和 TypeVar 是正确的方法吗?

Posted

技术标签:

【中文标题】基类和继承类的类型注释 - Generic 和 TypeVar 是正确的方法吗?【英文标题】:Type annotations for base and inherited classes - is Generic and TypeVar the right approach? 【发布时间】:2021-10-09 11:42:50 【问题描述】:

假设我有一个基类

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(...)

但是为什么它会沉默 - 我没有在那里给 mypy 任何新信息?我想得越多,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】:

这里发生了两件事

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 参数。此外,正如您在问题中所说,从 MyPy 或其他阅读您的代码的人的角度来看,从 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 开心的注释,即使是在最严格的设置下,它们甚至对人类也有意义!

注意我在这里更改了您代码中的另外两件事:

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

【讨论】:

以上是关于基类和继承类的类型注释 - Generic 和 TypeVar 是正确的方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++基类和派生类的构造函数

C++中的基类和派生类

基类和继承类的 XML 序列化信息

c++类继承

c++类继承

c++类继承